mirror of https://gitee.com/openkylin/qemu.git
Block layer patches
-----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJaoqOgAAoJEH8JsnLIjy/W2MMP/1gj7CJgtSG9wIzyBHjSWQMy ofXEgRJO9t/smfUMlH2NdrW8P2LvYcmqOsEBkLJzCtl48fPexwtI/cunzVjutXcf VlpqKz/8uN4C9D6m8FN/5kKf65l+tnVqnCoJgwafY5uT7jmoC8LF1xO2jo8a+lJd 0Dv6RxJUQq/tDR6OvO6aW4EzbOUcD4wkLvi/uz8+ZjV1BLSLlpdudejr6W9TnJY/ EGFedbxqjPV7fIvMbodbFp0Ie8Aw0WEL8ttERboeR4jbA/o+PZVGpPtHsr/4V6QO Pgh6vH2rGavxFzwuCWEGhlLKGx66CGqqdTknm6lNJchepCvcfoYxjOPZv9FCaMUs enC/x43xSkCmkwBwKKxpXqu1vS5nGdMebAwRjstSIplypjv2YOwS1AiU5snaDwuk t9Gjkw0Wka5nySuYi43H2RPXmlWbh4T8DfQ6pOyJGvXGjm8t+f5BTaMtSWn6Iq2W F6r1UezQJBDnUbpFgsRg4AP+htPGDHgsOg7KzCCd/lBHwbjX7dkQlAYbBZZ2OBF+ wQN5olDR6jsKIy2IlARNgNweZHW5UQa1cc+7HlVNNE5tqtkjo7aWPk/LhEzBCIHg sWG3VH2y3lQlaMzYh1v+jnGrFoq1ZJU4sbjaxvQX8czjmaQvPtbzKuZAovQ4pGwa g0SrWP6p9yLo0LXLuXBP =WDF4 -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging Block layer patches # gpg: Signature made Fri 09 Mar 2018 15:09:20 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: (56 commits) qemu-iotests: fix 203 migration completion race iotests: Tweak 030 in order to trigger a race condition with parallel jobs iotests: Skip test for ENOMEM error iotests: Mark all tests executable iotests: Test creating overlay when guest running qemu-iotests: Test ssh image creation over QMP qemu-iotests: Test qcow2 over file image creation with QMP block: Fail bdrv_truncate() with negative size file-posix: Fix no-op bdrv_truncate() with falloc preallocation ssh: Support .bdrv_co_create ssh: Pass BlockdevOptionsSsh to connect_to_ssh() ssh: QAPIfy host-key-check option ssh: Use QAPI BlockdevOptionsSsh object sheepdog: Support .bdrv_co_create sheepdog: QAPIfy "redundancy" create option nfs: Support .bdrv_co_create nfs: Use QAPI options in nfs_client_open() rbd: Use qemu_rbd_connect() in qemu_rbd_do_create() rbd: Assign s->snap/image_name in qemu_rbd_open() rbd: Support .bdrv_co_create ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
12c06d6f96
138
block.c
138
block.c
|
@ -34,6 +34,8 @@
|
|||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "qapi/qobject-output-visitor.h"
|
||||
#include "qapi/qapi-visit-block-core.h"
|
||||
#include "sysemu/block-backend.h"
|
||||
#include "sysemu/sysemu.h"
|
||||
#include "qemu/notify.h"
|
||||
|
@ -368,7 +370,7 @@ BlockDriver *bdrv_find_format(const char *format_name)
|
|||
return bdrv_do_find_format(format_name);
|
||||
}
|
||||
|
||||
static int bdrv_is_whitelisted(BlockDriver *drv, bool read_only)
|
||||
int bdrv_is_whitelisted(BlockDriver *drv, bool read_only)
|
||||
{
|
||||
static const char *whitelist_rw[] = {
|
||||
CONFIG_BDRV_RW_WHITELIST
|
||||
|
@ -2406,6 +2408,51 @@ BdrvChild *bdrv_open_child(const char *filename,
|
|||
return c;
|
||||
}
|
||||
|
||||
/* TODO Future callers may need to specify parent/child_role in order for
|
||||
* option inheritance to work. Existing callers use it for the root node. */
|
||||
BlockDriverState *bdrv_open_blockdev_ref(BlockdevRef *ref, Error **errp)
|
||||
{
|
||||
BlockDriverState *bs = NULL;
|
||||
Error *local_err = NULL;
|
||||
QObject *obj = NULL;
|
||||
QDict *qdict = NULL;
|
||||
const char *reference = NULL;
|
||||
Visitor *v = NULL;
|
||||
|
||||
if (ref->type == QTYPE_QSTRING) {
|
||||
reference = ref->u.reference;
|
||||
} else {
|
||||
BlockdevOptions *options = &ref->u.definition;
|
||||
assert(ref->type == QTYPE_QDICT);
|
||||
|
||||
v = qobject_output_visitor_new(&obj);
|
||||
visit_type_BlockdevOptions(v, NULL, &options, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
goto fail;
|
||||
}
|
||||
visit_complete(v, &obj);
|
||||
|
||||
qdict = qobject_to_qdict(obj);
|
||||
qdict_flatten(qdict);
|
||||
|
||||
/* bdrv_open_inherit() defaults to the values in bdrv_flags (for
|
||||
* compatibility with other callers) rather than what we want as the
|
||||
* real defaults. Apply the defaults here instead. */
|
||||
qdict_set_default_str(qdict, BDRV_OPT_CACHE_DIRECT, "off");
|
||||
qdict_set_default_str(qdict, BDRV_OPT_CACHE_NO_FLUSH, "off");
|
||||
qdict_set_default_str(qdict, BDRV_OPT_READ_ONLY, "off");
|
||||
}
|
||||
|
||||
bs = bdrv_open_inherit(NULL, reference, qdict, 0, NULL, NULL, errp);
|
||||
obj = NULL;
|
||||
|
||||
fail:
|
||||
qobject_decref(obj);
|
||||
visit_free(v);
|
||||
return bs;
|
||||
}
|
||||
|
||||
static BlockDriverState *bdrv_append_temp_snapshot(BlockDriverState *bs,
|
||||
int flags,
|
||||
QDict *snapshot_options,
|
||||
|
@ -3455,17 +3502,54 @@ static void bdrv_delete(BlockDriverState *bs)
|
|||
* free of errors) or -errno when an internal error occurred. The results of the
|
||||
* check are stored in res.
|
||||
*/
|
||||
int bdrv_check(BlockDriverState *bs, BdrvCheckResult *res, BdrvCheckMode fix)
|
||||
static int coroutine_fn bdrv_co_check(BlockDriverState *bs,
|
||||
BdrvCheckResult *res, BdrvCheckMode fix)
|
||||
{
|
||||
if (bs->drv == NULL) {
|
||||
return -ENOMEDIUM;
|
||||
}
|
||||
if (bs->drv->bdrv_check == NULL) {
|
||||
if (bs->drv->bdrv_co_check == NULL) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
memset(res, 0, sizeof(*res));
|
||||
return bs->drv->bdrv_check(bs, res, fix);
|
||||
return bs->drv->bdrv_co_check(bs, res, fix);
|
||||
}
|
||||
|
||||
typedef struct CheckCo {
|
||||
BlockDriverState *bs;
|
||||
BdrvCheckResult *res;
|
||||
BdrvCheckMode fix;
|
||||
int ret;
|
||||
} CheckCo;
|
||||
|
||||
static void bdrv_check_co_entry(void *opaque)
|
||||
{
|
||||
CheckCo *cco = opaque;
|
||||
cco->ret = bdrv_co_check(cco->bs, cco->res, cco->fix);
|
||||
}
|
||||
|
||||
int bdrv_check(BlockDriverState *bs,
|
||||
BdrvCheckResult *res, BdrvCheckMode fix)
|
||||
{
|
||||
Coroutine *co;
|
||||
CheckCo cco = {
|
||||
.bs = bs,
|
||||
.res = res,
|
||||
.ret = -EINPROGRESS,
|
||||
.fix = fix,
|
||||
};
|
||||
|
||||
if (qemu_in_coroutine()) {
|
||||
/* Fast-path if already in coroutine context */
|
||||
bdrv_check_co_entry(&cco);
|
||||
} else {
|
||||
co = qemu_coroutine_create(bdrv_check_co_entry, &cco);
|
||||
qemu_coroutine_enter(co);
|
||||
BDRV_POLL_WHILE(bs, cco.ret == -EINPROGRESS);
|
||||
}
|
||||
|
||||
return cco.ret;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -3635,6 +3719,11 @@ int bdrv_truncate(BdrvChild *child, int64_t offset, PreallocMode prealloc,
|
|||
error_setg(errp, "No medium inserted");
|
||||
return -ENOMEDIUM;
|
||||
}
|
||||
if (offset < 0) {
|
||||
error_setg(errp, "Image size cannot be negative");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!drv->bdrv_truncate) {
|
||||
if (bs->file && drv->is_filter) {
|
||||
return bdrv_truncate(bs->file, offset, prealloc, errp);
|
||||
|
@ -4209,7 +4298,8 @@ void bdrv_init_with_whitelist(void)
|
|||
bdrv_init();
|
||||
}
|
||||
|
||||
void bdrv_invalidate_cache(BlockDriverState *bs, Error **errp)
|
||||
static void coroutine_fn bdrv_co_invalidate_cache(BlockDriverState *bs,
|
||||
Error **errp)
|
||||
{
|
||||
BdrvChild *child, *parent;
|
||||
uint64_t perm, shared_perm;
|
||||
|
@ -4225,7 +4315,7 @@ void bdrv_invalidate_cache(BlockDriverState *bs, Error **errp)
|
|||
}
|
||||
|
||||
QLIST_FOREACH(child, &bs->children, next) {
|
||||
bdrv_invalidate_cache(child->bs, &local_err);
|
||||
bdrv_co_invalidate_cache(child->bs, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
return;
|
||||
|
@ -4255,8 +4345,8 @@ void bdrv_invalidate_cache(BlockDriverState *bs, Error **errp)
|
|||
}
|
||||
bdrv_set_perm(bs, perm, shared_perm);
|
||||
|
||||
if (bs->drv->bdrv_invalidate_cache) {
|
||||
bs->drv->bdrv_invalidate_cache(bs, &local_err);
|
||||
if (bs->drv->bdrv_co_invalidate_cache) {
|
||||
bs->drv->bdrv_co_invalidate_cache(bs, &local_err);
|
||||
if (local_err) {
|
||||
bs->open_flags |= BDRV_O_INACTIVE;
|
||||
error_propagate(errp, local_err);
|
||||
|
@ -4282,6 +4372,38 @@ void bdrv_invalidate_cache(BlockDriverState *bs, Error **errp)
|
|||
}
|
||||
}
|
||||
|
||||
typedef struct InvalidateCacheCo {
|
||||
BlockDriverState *bs;
|
||||
Error **errp;
|
||||
bool done;
|
||||
} InvalidateCacheCo;
|
||||
|
||||
static void coroutine_fn bdrv_invalidate_cache_co_entry(void *opaque)
|
||||
{
|
||||
InvalidateCacheCo *ico = opaque;
|
||||
bdrv_co_invalidate_cache(ico->bs, ico->errp);
|
||||
ico->done = true;
|
||||
}
|
||||
|
||||
void bdrv_invalidate_cache(BlockDriverState *bs, Error **errp)
|
||||
{
|
||||
Coroutine *co;
|
||||
InvalidateCacheCo ico = {
|
||||
.bs = bs,
|
||||
.done = false,
|
||||
.errp = errp
|
||||
};
|
||||
|
||||
if (qemu_in_coroutine()) {
|
||||
/* Fast-path if already in coroutine context */
|
||||
bdrv_invalidate_cache_co_entry(&ico);
|
||||
} else {
|
||||
co = qemu_coroutine_create(bdrv_invalidate_cache_co_entry, &ico);
|
||||
qemu_coroutine_enter(co);
|
||||
BDRV_POLL_WHILE(bs, !ico.done);
|
||||
}
|
||||
}
|
||||
|
||||
void bdrv_invalidate_cache_all(Error **errp)
|
||||
{
|
||||
BlockDriverState *bs;
|
||||
|
|
|
@ -9,7 +9,7 @@ block-obj-y += block-backend.o snapshot.o qapi.o
|
|||
block-obj-$(CONFIG_WIN32) += file-win32.o win32-aio.o
|
||||
block-obj-$(CONFIG_POSIX) += file-posix.o
|
||||
block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o
|
||||
block-obj-y += null.o mirror.o commit.o io.o
|
||||
block-obj-y += null.o mirror.o commit.o io.o create.o
|
||||
block-obj-y += throttle-groups.o
|
||||
block-obj-$(CONFIG_LINUX) += nvme.o
|
||||
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Block layer code related to image creation
|
||||
*
|
||||
* Copyright (c) 2018 Kevin Wolf <kwolf@redhat.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "block/block_int.h"
|
||||
#include "qapi/qapi-commands-block-core.h"
|
||||
#include "qapi/error.h"
|
||||
|
||||
typedef struct BlockdevCreateCo {
|
||||
BlockDriver *drv;
|
||||
BlockdevCreateOptions *opts;
|
||||
int ret;
|
||||
Error **errp;
|
||||
} BlockdevCreateCo;
|
||||
|
||||
static void coroutine_fn bdrv_co_create_co_entry(void *opaque)
|
||||
{
|
||||
BlockdevCreateCo *cco = opaque;
|
||||
cco->ret = cco->drv->bdrv_co_create(cco->opts, cco->errp);
|
||||
}
|
||||
|
||||
void qmp_x_blockdev_create(BlockdevCreateOptions *options, Error **errp)
|
||||
{
|
||||
const char *fmt = BlockdevDriver_str(options->driver);
|
||||
BlockDriver *drv = bdrv_find_format(fmt);
|
||||
Coroutine *co;
|
||||
BlockdevCreateCo cco;
|
||||
|
||||
/* If the driver is in the schema, we know that it exists. But it may not
|
||||
* be whitelisted. */
|
||||
assert(drv);
|
||||
if (bdrv_uses_whitelist() && !bdrv_is_whitelisted(drv, false)) {
|
||||
error_setg(errp, "Driver is not whitelisted");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Call callback if it exists */
|
||||
if (!drv->bdrv_co_create) {
|
||||
error_setg(errp, "Driver does not support blockdev-create");
|
||||
return;
|
||||
}
|
||||
|
||||
cco = (BlockdevCreateCo) {
|
||||
.drv = drv,
|
||||
.opts = options,
|
||||
.ret = -EINPROGRESS,
|
||||
.errp = errp,
|
||||
};
|
||||
|
||||
co = qemu_coroutine_create(bdrv_co_create_co_entry, &cco);
|
||||
qemu_coroutine_enter(co);
|
||||
while (cco.ret == -EINPROGRESS) {
|
||||
aio_poll(qemu_get_aio_context(), true);
|
||||
}
|
||||
}
|
|
@ -384,6 +384,12 @@ static void block_crypto_close(BlockDriverState *bs)
|
|||
qcrypto_block_free(crypto->block);
|
||||
}
|
||||
|
||||
static int block_crypto_reopen_prepare(BDRVReopenState *state,
|
||||
BlockReopenQueue *queue, Error **errp)
|
||||
{
|
||||
/* nothing needs checking */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1 MB bounce buffer gives good performance / memory tradeoff
|
||||
|
@ -621,6 +627,7 @@ BlockDriver bdrv_crypto_luks = {
|
|||
.bdrv_truncate = block_crypto_truncate,
|
||||
.create_opts = &block_crypto_create_opts_luks,
|
||||
|
||||
.bdrv_reopen_prepare = block_crypto_reopen_prepare,
|
||||
.bdrv_refresh_limits = block_crypto_refresh_limits,
|
||||
.bdrv_co_preadv = block_crypto_co_preadv,
|
||||
.bdrv_co_pwritev = block_crypto_co_pwritev,
|
||||
|
|
|
@ -1686,11 +1686,15 @@ static int raw_regular_truncate(int fd, int64_t offset, PreallocMode prealloc,
|
|||
* file systems that do not support fallocate(), trying to check if a
|
||||
* block is allocated before allocating it, so don't do that here.
|
||||
*/
|
||||
result = -posix_fallocate(fd, current_length, offset - current_length);
|
||||
if (result != 0) {
|
||||
/* posix_fallocate() doesn't set errno. */
|
||||
error_setg_errno(errp, -result,
|
||||
"Could not preallocate new data");
|
||||
if (offset != current_length) {
|
||||
result = -posix_fallocate(fd, current_length, offset - current_length);
|
||||
if (result != 0) {
|
||||
/* posix_fallocate() doesn't set errno. */
|
||||
error_setg_errno(errp, -result,
|
||||
"Could not preallocate new data");
|
||||
}
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
goto out;
|
||||
#endif
|
||||
|
@ -1982,34 +1986,25 @@ static int64_t raw_get_allocated_file_size(BlockDriverState *bs)
|
|||
return (int64_t)st.st_blocks * 512;
|
||||
}
|
||||
|
||||
static int coroutine_fn raw_co_create_opts(const char *filename, QemuOpts *opts,
|
||||
Error **errp)
|
||||
static int raw_co_create(BlockdevCreateOptions *options, Error **errp)
|
||||
{
|
||||
BlockdevCreateOptionsFile *file_opts;
|
||||
int fd;
|
||||
int result = 0;
|
||||
int64_t total_size = 0;
|
||||
bool nocow = false;
|
||||
PreallocMode prealloc;
|
||||
char *buf = NULL;
|
||||
Error *local_err = NULL;
|
||||
|
||||
strstart(filename, "file:", &filename);
|
||||
/* Validate options and set default values */
|
||||
assert(options->driver == BLOCKDEV_DRIVER_FILE);
|
||||
file_opts = &options->u.file;
|
||||
|
||||
/* Read out options */
|
||||
total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
||||
BDRV_SECTOR_SIZE);
|
||||
nocow = qemu_opt_get_bool(opts, BLOCK_OPT_NOCOW, false);
|
||||
buf = qemu_opt_get_del(opts, BLOCK_OPT_PREALLOC);
|
||||
prealloc = qapi_enum_parse(&PreallocMode_lookup, buf,
|
||||
PREALLOC_MODE_OFF, &local_err);
|
||||
g_free(buf);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
result = -EINVAL;
|
||||
goto out;
|
||||
if (!file_opts->has_nocow) {
|
||||
file_opts->nocow = false;
|
||||
}
|
||||
if (!file_opts->has_preallocation) {
|
||||
file_opts->preallocation = PREALLOC_MODE_OFF;
|
||||
}
|
||||
|
||||
fd = qemu_open(filename, O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
|
||||
/* Create file */
|
||||
fd = qemu_open(file_opts->filename, O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
|
||||
0644);
|
||||
if (fd < 0) {
|
||||
result = -errno;
|
||||
|
@ -2017,7 +2012,7 @@ static int coroutine_fn raw_co_create_opts(const char *filename, QemuOpts *opts,
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (nocow) {
|
||||
if (file_opts->nocow) {
|
||||
#ifdef __linux__
|
||||
/* Set NOCOW flag to solve performance issue on fs like btrfs.
|
||||
* This is an optimisation. The FS_IOC_SETFLAGS ioctl return value
|
||||
|
@ -2032,7 +2027,8 @@ static int coroutine_fn raw_co_create_opts(const char *filename, QemuOpts *opts,
|
|||
#endif
|
||||
}
|
||||
|
||||
result = raw_regular_truncate(fd, total_size, prealloc, errp);
|
||||
result = raw_regular_truncate(fd, file_opts->size, file_opts->preallocation,
|
||||
errp);
|
||||
if (result < 0) {
|
||||
goto out_close;
|
||||
}
|
||||
|
@ -2046,6 +2042,46 @@ out:
|
|||
return result;
|
||||
}
|
||||
|
||||
static int coroutine_fn raw_co_create_opts(const char *filename, QemuOpts *opts,
|
||||
Error **errp)
|
||||
{
|
||||
BlockdevCreateOptions options;
|
||||
int64_t total_size = 0;
|
||||
bool nocow = false;
|
||||
PreallocMode prealloc;
|
||||
char *buf = NULL;
|
||||
Error *local_err = NULL;
|
||||
|
||||
/* Skip file: protocol prefix */
|
||||
strstart(filename, "file:", &filename);
|
||||
|
||||
/* Read out options */
|
||||
total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
||||
BDRV_SECTOR_SIZE);
|
||||
nocow = qemu_opt_get_bool(opts, BLOCK_OPT_NOCOW, false);
|
||||
buf = qemu_opt_get_del(opts, BLOCK_OPT_PREALLOC);
|
||||
prealloc = qapi_enum_parse(&PreallocMode_lookup, buf,
|
||||
PREALLOC_MODE_OFF, &local_err);
|
||||
g_free(buf);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
options = (BlockdevCreateOptions) {
|
||||
.driver = BLOCKDEV_DRIVER_FILE,
|
||||
.u.file = {
|
||||
.filename = (char *) filename,
|
||||
.size = total_size,
|
||||
.has_preallocation = true,
|
||||
.preallocation = prealloc,
|
||||
.has_nocow = true,
|
||||
.nocow = nocow,
|
||||
},
|
||||
};
|
||||
return raw_co_create(&options, errp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find allocation range in @bs around offset @start.
|
||||
* May change underlying file descriptor's file offset.
|
||||
|
@ -2277,6 +2313,7 @@ BlockDriver bdrv_file = {
|
|||
.bdrv_reopen_commit = raw_reopen_commit,
|
||||
.bdrv_reopen_abort = raw_reopen_abort,
|
||||
.bdrv_close = raw_close,
|
||||
.bdrv_co_create = raw_co_create,
|
||||
.bdrv_co_create_opts = raw_co_create_opts,
|
||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
||||
.bdrv_co_block_status = raw_co_block_status,
|
||||
|
|
|
@ -553,10 +553,40 @@ static int64_t raw_get_allocated_file_size(BlockDriverState *bs)
|
|||
return st.st_size;
|
||||
}
|
||||
|
||||
static int raw_co_create(BlockdevCreateOptions *options, Error **errp)
|
||||
{
|
||||
BlockdevCreateOptionsFile *file_opts;
|
||||
int fd;
|
||||
|
||||
assert(options->driver == BLOCKDEV_DRIVER_FILE);
|
||||
file_opts = &options->u.file;
|
||||
|
||||
if (file_opts->has_preallocation) {
|
||||
error_setg(errp, "Preallocation is not supported on Windows");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (file_opts->has_nocow) {
|
||||
error_setg(errp, "nocow is not supported on Windows");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
fd = qemu_open(file_opts->filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
|
||||
0644);
|
||||
if (fd < 0) {
|
||||
error_setg_errno(errp, errno, "Could not create file");
|
||||
return -EIO;
|
||||
}
|
||||
set_sparse(fd);
|
||||
ftruncate(fd, file_opts->size);
|
||||
qemu_close(fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int coroutine_fn raw_co_create_opts(const char *filename, QemuOpts *opts,
|
||||
Error **errp)
|
||||
{
|
||||
int fd;
|
||||
BlockdevCreateOptions options;
|
||||
int64_t total_size = 0;
|
||||
|
||||
strstart(filename, "file:", &filename);
|
||||
|
@ -565,19 +595,18 @@ static int coroutine_fn raw_co_create_opts(const char *filename, QemuOpts *opts,
|
|||
total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
||||
BDRV_SECTOR_SIZE);
|
||||
|
||||
fd = qemu_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
|
||||
0644);
|
||||
if (fd < 0) {
|
||||
error_setg_errno(errp, errno, "Could not create file");
|
||||
return -EIO;
|
||||
}
|
||||
set_sparse(fd);
|
||||
ftruncate(fd, total_size);
|
||||
qemu_close(fd);
|
||||
return 0;
|
||||
options = (BlockdevCreateOptions) {
|
||||
.driver = BLOCKDEV_DRIVER_FILE,
|
||||
.u.file = {
|
||||
.filename = (char *) filename,
|
||||
.size = total_size,
|
||||
.has_preallocation = false,
|
||||
.has_nocow = false,
|
||||
},
|
||||
};
|
||||
return raw_co_create(&options, errp);
|
||||
}
|
||||
|
||||
|
||||
static QemuOptsList raw_create_opts = {
|
||||
.name = "raw-create-opts",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(raw_create_opts.head),
|
||||
|
|
135
block/gluster.c
135
block/gluster.c
|
@ -655,9 +655,11 @@ out:
|
|||
return -errno;
|
||||
}
|
||||
|
||||
static struct glfs *qemu_gluster_init(BlockdevOptionsGluster *gconf,
|
||||
const char *filename,
|
||||
QDict *options, Error **errp)
|
||||
/* Converts options given in @filename and the @options QDict into the QAPI
|
||||
* object @gconf. */
|
||||
static int qemu_gluster_parse(BlockdevOptionsGluster *gconf,
|
||||
const char *filename,
|
||||
QDict *options, Error **errp)
|
||||
{
|
||||
int ret;
|
||||
if (filename) {
|
||||
|
@ -668,8 +670,7 @@ static struct glfs *qemu_gluster_init(BlockdevOptionsGluster *gconf,
|
|||
"[host[:port]]volume/path[?socket=...]"
|
||||
"[,file.debug=N]"
|
||||
"[,file.logfile=/path/filename.log]\n");
|
||||
errno = -ret;
|
||||
return NULL;
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
ret = qemu_gluster_parse_json(gconf, options, errp);
|
||||
|
@ -685,10 +686,23 @@ static struct glfs *qemu_gluster_init(BlockdevOptionsGluster *gconf,
|
|||
"file.server.1.transport=unix,"
|
||||
"file.server.1.socket=/var/run/glusterd.socket ..."
|
||||
"\n");
|
||||
errno = -ret;
|
||||
return NULL;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct glfs *qemu_gluster_init(BlockdevOptionsGluster *gconf,
|
||||
const char *filename,
|
||||
QDict *options, Error **errp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = qemu_gluster_parse(gconf, filename, options, errp);
|
||||
if (ret < 0) {
|
||||
errno = -ret;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return qemu_gluster_glfs_init(gconf, errp);
|
||||
|
@ -1021,20 +1035,72 @@ static int qemu_gluster_do_truncate(struct glfs_fd *fd, int64_t offset,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int qemu_gluster_co_create(BlockdevCreateOptions *options,
|
||||
Error **errp)
|
||||
{
|
||||
BlockdevCreateOptionsGluster *opts = &options->u.gluster;
|
||||
struct glfs *glfs;
|
||||
struct glfs_fd *fd = NULL;
|
||||
int ret = 0;
|
||||
|
||||
assert(options->driver == BLOCKDEV_DRIVER_GLUSTER);
|
||||
|
||||
glfs = qemu_gluster_glfs_init(opts->location, errp);
|
||||
if (!glfs) {
|
||||
ret = -errno;
|
||||
goto out;
|
||||
}
|
||||
|
||||
fd = glfs_creat(glfs, opts->location->path,
|
||||
O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR);
|
||||
if (!fd) {
|
||||
ret = -errno;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = qemu_gluster_do_truncate(fd, opts->size, opts->preallocation, errp);
|
||||
|
||||
out:
|
||||
if (fd) {
|
||||
if (glfs_close(fd) != 0 && ret == 0) {
|
||||
ret = -errno;
|
||||
}
|
||||
}
|
||||
glfs_clear_preopened(glfs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int coroutine_fn qemu_gluster_co_create_opts(const char *filename,
|
||||
QemuOpts *opts,
|
||||
Error **errp)
|
||||
{
|
||||
BlockdevCreateOptions *options;
|
||||
BlockdevCreateOptionsGluster *gopts;
|
||||
BlockdevOptionsGluster *gconf;
|
||||
struct glfs *glfs;
|
||||
struct glfs_fd *fd = NULL;
|
||||
int ret = 0;
|
||||
PreallocMode prealloc;
|
||||
int64_t total_size = 0;
|
||||
char *tmp = NULL;
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
|
||||
options = g_new0(BlockdevCreateOptions, 1);
|
||||
options->driver = BLOCKDEV_DRIVER_GLUSTER;
|
||||
gopts = &options->u.gluster;
|
||||
|
||||
gconf = g_new0(BlockdevOptionsGluster, 1);
|
||||
gopts->location = gconf;
|
||||
|
||||
gopts->size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
||||
BDRV_SECTOR_SIZE);
|
||||
|
||||
tmp = qemu_opt_get_del(opts, BLOCK_OPT_PREALLOC);
|
||||
gopts->preallocation = qapi_enum_parse(&PreallocMode_lookup, tmp,
|
||||
PREALLOC_MODE_OFF, &local_err);
|
||||
g_free(tmp);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
gconf->debug = qemu_opt_get_number_del(opts, GLUSTER_OPT_DEBUG,
|
||||
GLUSTER_DEBUG_DEFAULT);
|
||||
if (gconf->debug < 0) {
|
||||
|
@ -1050,42 +1116,19 @@ static int coroutine_fn qemu_gluster_co_create_opts(const char *filename,
|
|||
}
|
||||
gconf->has_logfile = true;
|
||||
|
||||
glfs = qemu_gluster_init(gconf, filename, NULL, errp);
|
||||
if (!glfs) {
|
||||
ret = -errno;
|
||||
goto out;
|
||||
ret = qemu_gluster_parse(gconf, filename, NULL, errp);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
||||
BDRV_SECTOR_SIZE);
|
||||
|
||||
tmp = qemu_opt_get_del(opts, BLOCK_OPT_PREALLOC);
|
||||
prealloc = qapi_enum_parse(&PreallocMode_lookup, tmp, PREALLOC_MODE_OFF,
|
||||
&local_err);
|
||||
g_free(tmp);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
ret = qemu_gluster_co_create(options, errp);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fd = glfs_creat(glfs, gconf->path,
|
||||
O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR);
|
||||
if (!fd) {
|
||||
ret = -errno;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = qemu_gluster_do_truncate(fd, total_size, prealloc, errp);
|
||||
|
||||
out:
|
||||
if (fd) {
|
||||
if (glfs_close(fd) != 0 && ret == 0) {
|
||||
ret = -errno;
|
||||
}
|
||||
}
|
||||
qapi_free_BlockdevOptionsGluster(gconf);
|
||||
glfs_clear_preopened(glfs);
|
||||
ret = 0;
|
||||
fail:
|
||||
qapi_free_BlockdevCreateOptions(options);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1436,6 +1479,7 @@ static BlockDriver bdrv_gluster = {
|
|||
.bdrv_reopen_commit = qemu_gluster_reopen_commit,
|
||||
.bdrv_reopen_abort = qemu_gluster_reopen_abort,
|
||||
.bdrv_close = qemu_gluster_close,
|
||||
.bdrv_co_create = qemu_gluster_co_create,
|
||||
.bdrv_co_create_opts = qemu_gluster_co_create_opts,
|
||||
.bdrv_getlength = qemu_gluster_getlength,
|
||||
.bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
|
||||
|
@ -1464,6 +1508,7 @@ static BlockDriver bdrv_gluster_tcp = {
|
|||
.bdrv_reopen_commit = qemu_gluster_reopen_commit,
|
||||
.bdrv_reopen_abort = qemu_gluster_reopen_abort,
|
||||
.bdrv_close = qemu_gluster_close,
|
||||
.bdrv_co_create = qemu_gluster_co_create,
|
||||
.bdrv_co_create_opts = qemu_gluster_co_create_opts,
|
||||
.bdrv_getlength = qemu_gluster_getlength,
|
||||
.bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
|
||||
|
@ -1492,6 +1537,7 @@ static BlockDriver bdrv_gluster_unix = {
|
|||
.bdrv_reopen_commit = qemu_gluster_reopen_commit,
|
||||
.bdrv_reopen_abort = qemu_gluster_reopen_abort,
|
||||
.bdrv_close = qemu_gluster_close,
|
||||
.bdrv_co_create = qemu_gluster_co_create,
|
||||
.bdrv_co_create_opts = qemu_gluster_co_create_opts,
|
||||
.bdrv_getlength = qemu_gluster_getlength,
|
||||
.bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
|
||||
|
@ -1526,6 +1572,7 @@ static BlockDriver bdrv_gluster_rdma = {
|
|||
.bdrv_reopen_commit = qemu_gluster_reopen_commit,
|
||||
.bdrv_reopen_abort = qemu_gluster_reopen_abort,
|
||||
.bdrv_close = qemu_gluster_close,
|
||||
.bdrv_co_create = qemu_gluster_co_create,
|
||||
.bdrv_co_create_opts = qemu_gluster_co_create_opts,
|
||||
.bdrv_getlength = qemu_gluster_getlength,
|
||||
.bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
|
||||
|
|
|
@ -2177,8 +2177,8 @@ static int iscsi_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void iscsi_invalidate_cache(BlockDriverState *bs,
|
||||
Error **errp)
|
||||
static void coroutine_fn iscsi_co_invalidate_cache(BlockDriverState *bs,
|
||||
Error **errp)
|
||||
{
|
||||
IscsiLun *iscsilun = bs->opaque;
|
||||
iscsi_allocmap_invalidate(iscsilun);
|
||||
|
@ -2209,7 +2209,7 @@ static BlockDriver bdrv_iscsi = {
|
|||
.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,
|
||||
|
|
244
block/nfs.c
244
block/nfs.c
|
@ -367,49 +367,6 @@ static int coroutine_fn nfs_co_flush(BlockDriverState *bs)
|
|||
return task.ret;
|
||||
}
|
||||
|
||||
static QemuOptsList runtime_opts = {
|
||||
.name = "nfs",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
||||
.desc = {
|
||||
{
|
||||
.name = "path",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Path of the image on the host",
|
||||
},
|
||||
{
|
||||
.name = "user",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "UID value to use when talking to the server",
|
||||
},
|
||||
{
|
||||
.name = "group",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "GID value to use when talking to the server",
|
||||
},
|
||||
{
|
||||
.name = "tcp-syn-count",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "Number of SYNs to send during the session establish",
|
||||
},
|
||||
{
|
||||
.name = "readahead-size",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "Set the readahead size in bytes",
|
||||
},
|
||||
{
|
||||
.name = "page-cache-size",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "Set the pagecache size in bytes",
|
||||
},
|
||||
{
|
||||
.name = "debug",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "Set the NFS debug level (max 2)",
|
||||
},
|
||||
{ /* end of list */ }
|
||||
},
|
||||
};
|
||||
|
||||
static void nfs_detach_aio_context(BlockDriverState *bs)
|
||||
{
|
||||
NFSClient *client = bs->opaque;
|
||||
|
@ -452,71 +409,16 @@ static void nfs_file_close(BlockDriverState *bs)
|
|||
nfs_client_close(client);
|
||||
}
|
||||
|
||||
static NFSServer *nfs_config(QDict *options, Error **errp)
|
||||
{
|
||||
NFSServer *server = NULL;
|
||||
QDict *addr = NULL;
|
||||
QObject *crumpled_addr = NULL;
|
||||
Visitor *iv = NULL;
|
||||
Error *local_error = NULL;
|
||||
|
||||
qdict_extract_subqdict(options, &addr, "server.");
|
||||
if (!qdict_size(addr)) {
|
||||
error_setg(errp, "NFS server address missing");
|
||||
goto out;
|
||||
}
|
||||
|
||||
crumpled_addr = qdict_crumple(addr, errp);
|
||||
if (!crumpled_addr) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Caution: this works only because all scalar members of
|
||||
* NFSServer are QString in @crumpled_addr. The visitor expects
|
||||
* @crumpled_addr to be typed according to the QAPI schema. It
|
||||
* is when @options come from -blockdev or blockdev_add. But when
|
||||
* they come from -drive, they're all QString.
|
||||
*/
|
||||
iv = qobject_input_visitor_new(crumpled_addr);
|
||||
visit_type_NFSServer(iv, NULL, &server, &local_error);
|
||||
if (local_error) {
|
||||
error_propagate(errp, local_error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
QDECREF(addr);
|
||||
qobject_decref(crumpled_addr);
|
||||
visit_free(iv);
|
||||
return server;
|
||||
}
|
||||
|
||||
|
||||
static int64_t nfs_client_open(NFSClient *client, QDict *options,
|
||||
static int64_t nfs_client_open(NFSClient *client, BlockdevOptionsNfs *opts,
|
||||
int flags, int open_flags, Error **errp)
|
||||
{
|
||||
int64_t ret = -EINVAL;
|
||||
QemuOpts *opts = NULL;
|
||||
Error *local_err = NULL;
|
||||
struct stat st;
|
||||
char *file = NULL, *strp = NULL;
|
||||
|
||||
qemu_mutex_init(&client->mutex);
|
||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
client->path = g_strdup(qemu_opt_get(opts, "path"));
|
||||
if (!client->path) {
|
||||
ret = -EINVAL;
|
||||
error_setg(errp, "No path was specified");
|
||||
goto fail;
|
||||
}
|
||||
client->path = g_strdup(opts->path);
|
||||
|
||||
strp = strrchr(client->path, '/');
|
||||
if (strp == NULL) {
|
||||
|
@ -526,12 +428,10 @@ static int64_t nfs_client_open(NFSClient *client, QDict *options,
|
|||
file = g_strdup(strp);
|
||||
*strp = 0;
|
||||
|
||||
/* Pop the config into our state object, Exit if invalid */
|
||||
client->server = nfs_config(options, errp);
|
||||
if (!client->server) {
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
/* Steal the NFSServer object from opts; set the original pointer to NULL
|
||||
* to avoid use after free and double free. */
|
||||
client->server = opts->server;
|
||||
opts->server = NULL;
|
||||
|
||||
client->context = nfs_init_context();
|
||||
if (client->context == NULL) {
|
||||
|
@ -539,29 +439,29 @@ static int64_t nfs_client_open(NFSClient *client, QDict *options,
|
|||
goto fail;
|
||||
}
|
||||
|
||||
if (qemu_opt_get(opts, "user")) {
|
||||
client->uid = qemu_opt_get_number(opts, "user", 0);
|
||||
if (opts->has_user) {
|
||||
client->uid = opts->user;
|
||||
nfs_set_uid(client->context, client->uid);
|
||||
}
|
||||
|
||||
if (qemu_opt_get(opts, "group")) {
|
||||
client->gid = qemu_opt_get_number(opts, "group", 0);
|
||||
if (opts->has_group) {
|
||||
client->gid = opts->group;
|
||||
nfs_set_gid(client->context, client->gid);
|
||||
}
|
||||
|
||||
if (qemu_opt_get(opts, "tcp-syn-count")) {
|
||||
client->tcp_syncnt = qemu_opt_get_number(opts, "tcp-syn-count", 0);
|
||||
if (opts->has_tcp_syn_count) {
|
||||
client->tcp_syncnt = opts->tcp_syn_count;
|
||||
nfs_set_tcp_syncnt(client->context, client->tcp_syncnt);
|
||||
}
|
||||
|
||||
#ifdef LIBNFS_FEATURE_READAHEAD
|
||||
if (qemu_opt_get(opts, "readahead-size")) {
|
||||
if (opts->has_readahead_size) {
|
||||
if (open_flags & BDRV_O_NOCACHE) {
|
||||
error_setg(errp, "Cannot enable NFS readahead "
|
||||
"if cache.direct = on");
|
||||
goto fail;
|
||||
}
|
||||
client->readahead = qemu_opt_get_number(opts, "readahead-size", 0);
|
||||
client->readahead = opts->readahead_size;
|
||||
if (client->readahead > QEMU_NFS_MAX_READAHEAD_SIZE) {
|
||||
warn_report("Truncating NFS readahead size to %d",
|
||||
QEMU_NFS_MAX_READAHEAD_SIZE);
|
||||
|
@ -576,13 +476,13 @@ static int64_t nfs_client_open(NFSClient *client, QDict *options,
|
|||
#endif
|
||||
|
||||
#ifdef LIBNFS_FEATURE_PAGECACHE
|
||||
if (qemu_opt_get(opts, "page-cache-size")) {
|
||||
if (opts->has_page_cache_size) {
|
||||
if (open_flags & BDRV_O_NOCACHE) {
|
||||
error_setg(errp, "Cannot enable NFS pagecache "
|
||||
"if cache.direct = on");
|
||||
goto fail;
|
||||
}
|
||||
client->pagecache = qemu_opt_get_number(opts, "page-cache-size", 0);
|
||||
client->pagecache = opts->page_cache_size;
|
||||
if (client->pagecache > QEMU_NFS_MAX_PAGECACHE_SIZE) {
|
||||
warn_report("Truncating NFS pagecache size to %d pages",
|
||||
QEMU_NFS_MAX_PAGECACHE_SIZE);
|
||||
|
@ -595,8 +495,8 @@ static int64_t nfs_client_open(NFSClient *client, QDict *options,
|
|||
#endif
|
||||
|
||||
#ifdef LIBNFS_FEATURE_DEBUG
|
||||
if (qemu_opt_get(opts, "debug")) {
|
||||
client->debug = qemu_opt_get_number(opts, "debug", 0);
|
||||
if (opts->has_debug) {
|
||||
client->debug = opts->debug;
|
||||
/* limit the maximum debug level to avoid potential flooding
|
||||
* of our log files. */
|
||||
if (client->debug > QEMU_NFS_MAX_DEBUG_LEVEL) {
|
||||
|
@ -647,11 +547,53 @@ static int64_t nfs_client_open(NFSClient *client, QDict *options,
|
|||
fail:
|
||||
nfs_client_close(client);
|
||||
out:
|
||||
qemu_opts_del(opts);
|
||||
g_free(file);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static BlockdevOptionsNfs *nfs_options_qdict_to_qapi(QDict *options,
|
||||
Error **errp)
|
||||
{
|
||||
BlockdevOptionsNfs *opts = NULL;
|
||||
QObject *crumpled = NULL;
|
||||
Visitor *v;
|
||||
Error *local_err = NULL;
|
||||
|
||||
crumpled = qdict_crumple(options, errp);
|
||||
if (crumpled == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
v = qobject_input_visitor_new_keyval(crumpled);
|
||||
visit_type_BlockdevOptionsNfs(v, NULL, &opts, &local_err);
|
||||
visit_free(v);
|
||||
qobject_decref(crumpled);
|
||||
|
||||
if (local_err) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
static int64_t nfs_client_open_qdict(NFSClient *client, QDict *options,
|
||||
int flags, int open_flags, Error **errp)
|
||||
{
|
||||
BlockdevOptionsNfs *opts;
|
||||
int ret;
|
||||
|
||||
opts = nfs_options_qdict_to_qapi(options, errp);
|
||||
if (opts == NULL) {
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = nfs_client_open(client, opts, flags, open_flags, errp);
|
||||
fail:
|
||||
qapi_free_BlockdevOptionsNfs(opts);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int nfs_file_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp) {
|
||||
NFSClient *client = bs->opaque;
|
||||
|
@ -659,9 +601,9 @@ static int nfs_file_open(BlockDriverState *bs, QDict *options, int flags,
|
|||
|
||||
client->aio_context = bdrv_get_aio_context(bs);
|
||||
|
||||
ret = nfs_client_open(client, options,
|
||||
(flags & BDRV_O_RDWR) ? O_RDWR : O_RDONLY,
|
||||
bs->open_flags, errp);
|
||||
ret = nfs_client_open_qdict(client, options,
|
||||
(flags & BDRV_O_RDWR) ? O_RDWR : O_RDONLY,
|
||||
bs->open_flags, errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
@ -684,18 +626,43 @@ static QemuOptsList nfs_create_opts = {
|
|||
}
|
||||
};
|
||||
|
||||
static int coroutine_fn nfs_file_co_create_opts(const char *url, QemuOpts *opts,
|
||||
Error **errp)
|
||||
static int nfs_file_co_create(BlockdevCreateOptions *options, Error **errp)
|
||||
{
|
||||
int64_t ret, total_size;
|
||||
BlockdevCreateOptionsNfs *opts = &options->u.nfs;
|
||||
NFSClient *client = g_new0(NFSClient, 1);
|
||||
QDict *options = NULL;
|
||||
int ret;
|
||||
|
||||
assert(options->driver == BLOCKDEV_DRIVER_NFS);
|
||||
|
||||
client->aio_context = qemu_get_aio_context();
|
||||
|
||||
ret = nfs_client_open(client, opts->location, O_CREAT, 0, errp);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
ret = nfs_ftruncate(client->context, client->fh, opts->size);
|
||||
nfs_client_close(client);
|
||||
|
||||
out:
|
||||
g_free(client);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int coroutine_fn nfs_file_co_create_opts(const char *url, QemuOpts *opts,
|
||||
Error **errp)
|
||||
{
|
||||
BlockdevCreateOptions *create_options;
|
||||
BlockdevCreateOptionsNfs *nfs_opts;
|
||||
QDict *options;
|
||||
int ret;
|
||||
|
||||
create_options = g_new0(BlockdevCreateOptions, 1);
|
||||
create_options->driver = BLOCKDEV_DRIVER_NFS;
|
||||
nfs_opts = &create_options->u.nfs;
|
||||
|
||||
/* Read out options */
|
||||
total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
||||
BDRV_SECTOR_SIZE);
|
||||
nfs_opts->size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
||||
BDRV_SECTOR_SIZE);
|
||||
|
||||
options = qdict_new();
|
||||
ret = nfs_parse_uri(url, options, errp);
|
||||
|
@ -703,15 +670,21 @@ static int coroutine_fn nfs_file_co_create_opts(const char *url, QemuOpts *opts,
|
|||
goto out;
|
||||
}
|
||||
|
||||
ret = nfs_client_open(client, options, O_CREAT, 0, errp);
|
||||
nfs_opts->location = nfs_options_qdict_to_qapi(options, errp);
|
||||
if (nfs_opts->location == NULL) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = nfs_file_co_create(create_options, errp);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
ret = nfs_ftruncate(client->context, client->fh, total_size);
|
||||
nfs_client_close(client);
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
QDECREF(options);
|
||||
g_free(client);
|
||||
qapi_free_BlockdevCreateOptions(create_options);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -876,8 +849,8 @@ static void nfs_refresh_filename(BlockDriverState *bs, QDict *options)
|
|||
}
|
||||
|
||||
#ifdef LIBNFS_FEATURE_PAGECACHE
|
||||
static void nfs_invalidate_cache(BlockDriverState *bs,
|
||||
Error **errp)
|
||||
static void coroutine_fn nfs_co_invalidate_cache(BlockDriverState *bs,
|
||||
Error **errp)
|
||||
{
|
||||
NFSClient *client = bs->opaque;
|
||||
nfs_pagecache_invalidate(client->context, client->fh);
|
||||
|
@ -898,6 +871,7 @@ static BlockDriver bdrv_nfs = {
|
|||
|
||||
.bdrv_file_open = nfs_file_open,
|
||||
.bdrv_close = nfs_file_close,
|
||||
.bdrv_co_create = nfs_file_co_create,
|
||||
.bdrv_co_create_opts = nfs_file_co_create_opts,
|
||||
.bdrv_reopen_prepare = nfs_reopen_prepare,
|
||||
|
||||
|
@ -910,7 +884,7 @@ static BlockDriver bdrv_nfs = {
|
|||
.bdrv_refresh_filename = nfs_refresh_filename,
|
||||
|
||||
#ifdef LIBNFS_FEATURE_PAGECACHE
|
||||
.bdrv_invalidate_cache = nfs_invalidate_cache,
|
||||
.bdrv_co_invalidate_cache = nfs_co_invalidate_cache,
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -378,8 +378,9 @@ static coroutine_fn int parallels_co_readv(BlockDriverState *bs,
|
|||
}
|
||||
|
||||
|
||||
static int parallels_check(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
BdrvCheckMode fix)
|
||||
static int coroutine_fn parallels_co_check(BlockDriverState *bs,
|
||||
BdrvCheckResult *res,
|
||||
BdrvCheckMode fix)
|
||||
{
|
||||
BDRVParallelsState *s = bs->opaque;
|
||||
int64_t size, prev_off, high_off;
|
||||
|
@ -394,6 +395,7 @@ static int parallels_check(BlockDriverState *bs, BdrvCheckResult *res,
|
|||
return size;
|
||||
}
|
||||
|
||||
qemu_co_mutex_lock(&s->lock);
|
||||
if (s->header_unclean) {
|
||||
fprintf(stderr, "%s image was not closed correctly\n",
|
||||
fix & BDRV_FIX_ERRORS ? "Repairing" : "ERROR");
|
||||
|
@ -442,11 +444,12 @@ static int parallels_check(BlockDriverState *bs, BdrvCheckResult *res,
|
|||
prev_off = off;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
if (flush_bat) {
|
||||
ret = bdrv_pwrite_sync(bs->file, 0, s->header, s->header_size);
|
||||
if (ret < 0) {
|
||||
res->check_errors++;
|
||||
return ret;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -465,13 +468,15 @@ static int parallels_check(BlockDriverState *bs, BdrvCheckResult *res,
|
|||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
res->check_errors++;
|
||||
return ret;
|
||||
goto out;
|
||||
}
|
||||
res->leaks_fixed += count;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
out:
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
@ -799,7 +804,7 @@ static BlockDriver bdrv_parallels = {
|
|||
.bdrv_co_writev = parallels_co_writev,
|
||||
.supports_backing = true,
|
||||
.bdrv_co_create_opts = parallels_co_create_opts,
|
||||
.bdrv_check = parallels_check,
|
||||
.bdrv_co_check = parallels_co_check,
|
||||
.create_opts = ¶llels_create_opts,
|
||||
};
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@ static int update_header_sync(BlockDriverState *bs)
|
|||
return ret;
|
||||
}
|
||||
|
||||
return bdrv_flush(bs);
|
||||
return bdrv_flush(bs->file->bs);
|
||||
}
|
||||
|
||||
static inline void bitmap_table_to_be(uint64_t *bitmap_table, size_t size)
|
||||
|
@ -882,7 +882,7 @@ static int update_ext_header_and_dir(BlockDriverState *bs,
|
|||
return ret;
|
||||
}
|
||||
|
||||
ret = bdrv_flush(bs->file->bs);
|
||||
ret = qcow2_flush_caches(bs);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "qemu/osdep.h"
|
||||
#include <zlib.h>
|
||||
|
||||
#include "qapi/error.h"
|
||||
#include "qemu-common.h"
|
||||
#include "block/block_int.h"
|
||||
#include "block/qcow2.h"
|
||||
|
@ -2092,11 +2093,21 @@ int qcow2_expand_zero_clusters(BlockDriverState *bs,
|
|||
}
|
||||
|
||||
for (i = 0; i < s->nb_snapshots; i++) {
|
||||
int l1_sectors = DIV_ROUND_UP(s->snapshots[i].l1_size *
|
||||
sizeof(uint64_t), BDRV_SECTOR_SIZE);
|
||||
int l1_size2;
|
||||
uint64_t *new_l1_table;
|
||||
Error *local_err = NULL;
|
||||
|
||||
uint64_t *new_l1_table =
|
||||
g_try_realloc(l1_table, l1_sectors * BDRV_SECTOR_SIZE);
|
||||
ret = qcow2_validate_table(bs, s->snapshots[i].l1_table_offset,
|
||||
s->snapshots[i].l1_size, sizeof(uint64_t),
|
||||
QCOW_MAX_L1_SIZE, "Snapshot L1 table",
|
||||
&local_err);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
l1_size2 = s->snapshots[i].l1_size * sizeof(uint64_t);
|
||||
new_l1_table = g_try_realloc(l1_table, l1_size2);
|
||||
|
||||
if (!new_l1_table) {
|
||||
ret = -ENOMEM;
|
||||
|
@ -2105,9 +2116,8 @@ int qcow2_expand_zero_clusters(BlockDriverState *bs,
|
|||
|
||||
l1_table = new_l1_table;
|
||||
|
||||
ret = bdrv_read(bs->file,
|
||||
s->snapshots[i].l1_table_offset / BDRV_SECTOR_SIZE,
|
||||
(void *)l1_table, l1_sectors);
|
||||
ret = bdrv_pread(bs->file, s->snapshots[i].l1_table_offset,
|
||||
l1_table, l1_size2);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
|
|
@ -1171,7 +1171,35 @@ void qcow2_free_any_clusters(BlockDriverState *bs, uint64_t l2_entry,
|
|||
}
|
||||
}
|
||||
|
||||
int coroutine_fn qcow2_write_caches(BlockDriverState *bs)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
int ret;
|
||||
|
||||
ret = qcow2_cache_write(bs, s->l2_table_cache);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (qcow2_need_accurate_refcounts(s)) {
|
||||
ret = qcow2_cache_write(bs, s->refcount_block_cache);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int coroutine_fn qcow2_flush_caches(BlockDriverState *bs)
|
||||
{
|
||||
int ret = qcow2_write_caches(bs);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return bdrv_flush(bs->file->bs);
|
||||
}
|
||||
|
||||
/*********************************************************/
|
||||
/* snapshots and image creation */
|
||||
|
@ -2019,6 +2047,20 @@ static int calculate_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
|||
/* snapshots */
|
||||
for (i = 0; i < s->nb_snapshots; i++) {
|
||||
sn = s->snapshots + i;
|
||||
if (offset_into_cluster(s, sn->l1_table_offset)) {
|
||||
fprintf(stderr, "ERROR snapshot %s (%s) l1_offset=%#" PRIx64 ": "
|
||||
"L1 table is not cluster aligned; snapshot table entry "
|
||||
"corrupted\n", sn->id_str, sn->name, sn->l1_table_offset);
|
||||
res->corruptions++;
|
||||
continue;
|
||||
}
|
||||
if (sn->l1_size > QCOW_MAX_L1_SIZE / sizeof(uint64_t)) {
|
||||
fprintf(stderr, "ERROR snapshot %s (%s) l1_size=%#" PRIx32 ": "
|
||||
"L1 table is too large; snapshot table entry corrupted\n",
|
||||
sn->id_str, sn->name, sn->l1_size);
|
||||
res->corruptions++;
|
||||
continue;
|
||||
}
|
||||
ret = check_refcounts_l1(bs, res, refcount_table, nb_clusters,
|
||||
sn->l1_table_offset, sn->l1_size, 0, fix);
|
||||
if (ret < 0) {
|
||||
|
@ -2614,9 +2656,17 @@ int qcow2_check_metadata_overlap(BlockDriverState *bs, int ign, int64_t offset,
|
|||
uint64_t l1_ofs = s->snapshots[i].l1_table_offset;
|
||||
uint32_t l1_sz = s->snapshots[i].l1_size;
|
||||
uint64_t l1_sz2 = l1_sz * sizeof(uint64_t);
|
||||
uint64_t *l1 = g_try_malloc(l1_sz2);
|
||||
uint64_t *l1;
|
||||
int ret;
|
||||
|
||||
ret = qcow2_validate_table(bs, l1_ofs, l1_sz, sizeof(uint64_t),
|
||||
QCOW_MAX_L1_SIZE, "", NULL);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
l1 = g_try_malloc(l1_sz2);
|
||||
|
||||
if (l1_sz2 && l1 == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
|
|
@ -465,6 +465,7 @@ int qcow2_snapshot_goto(BlockDriverState *bs, const char *snapshot_id)
|
|||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
QCowSnapshot *sn;
|
||||
Error *local_err = NULL;
|
||||
int i, snapshot_index;
|
||||
int cur_l1_bytes, sn_l1_bytes;
|
||||
int ret;
|
||||
|
@ -477,6 +478,14 @@ int qcow2_snapshot_goto(BlockDriverState *bs, const char *snapshot_id)
|
|||
}
|
||||
sn = &s->snapshots[snapshot_index];
|
||||
|
||||
ret = qcow2_validate_table(bs, sn->l1_table_offset, sn->l1_size,
|
||||
sizeof(uint64_t), QCOW_MAX_L1_SIZE,
|
||||
"Snapshot L1 table", &local_err);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (sn->disk_size != bs->total_sectors * BDRV_SECTOR_SIZE) {
|
||||
error_report("qcow2: Loading snapshots with different disk "
|
||||
"size is not implemented");
|
||||
|
@ -602,6 +611,13 @@ int qcow2_snapshot_delete(BlockDriverState *bs,
|
|||
}
|
||||
sn = s->snapshots[snapshot_index];
|
||||
|
||||
ret = qcow2_validate_table(bs, sn.l1_table_offset, sn.l1_size,
|
||||
sizeof(uint64_t), QCOW_MAX_L1_SIZE,
|
||||
"Snapshot L1 table", errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Remove it from the snapshot list */
|
||||
memmove(s->snapshots + snapshot_index,
|
||||
s->snapshots + snapshot_index + 1,
|
||||
|
@ -704,9 +720,11 @@ int qcow2_snapshot_load_tmp(BlockDriverState *bs,
|
|||
sn = &s->snapshots[snapshot_index];
|
||||
|
||||
/* Allocate and read in the snapshot's L1 table */
|
||||
if (sn->l1_size > QCOW_MAX_L1_SIZE / sizeof(uint64_t)) {
|
||||
error_setg(errp, "Snapshot L1 table too large");
|
||||
return -EFBIG;
|
||||
ret = qcow2_validate_table(bs, sn->l1_table_offset, sn->l1_size,
|
||||
sizeof(uint64_t), QCOW_MAX_L1_SIZE,
|
||||
"Snapshot L1 table", errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
new_l1_bytes = sn->l1_size * sizeof(uint64_t);
|
||||
new_l1_table = qemu_try_blockalign(bs->file->bs,
|
||||
|
|
580
block/qcow2.c
580
block/qcow2.c
|
@ -37,7 +37,8 @@
|
|||
#include "qemu/option_int.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qemu/bswap.h"
|
||||
#include "qapi/opts-visitor.h"
|
||||
#include "qapi/qobject-input-visitor.h"
|
||||
#include "qapi/qapi-visit-block-core.h"
|
||||
#include "block/crypto.h"
|
||||
|
||||
/*
|
||||
|
@ -500,7 +501,7 @@ static int qcow2_mark_clean(BlockDriverState *bs)
|
|||
|
||||
s->incompatible_features &= ~QCOW2_INCOMPAT_DIRTY;
|
||||
|
||||
ret = bdrv_flush(bs);
|
||||
ret = qcow2_flush_caches(bs);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
@ -530,7 +531,7 @@ int qcow2_mark_consistent(BlockDriverState *bs)
|
|||
BDRVQcow2State *s = bs->opaque;
|
||||
|
||||
if (s->incompatible_features & QCOW2_INCOMPAT_CORRUPT) {
|
||||
int ret = bdrv_flush(bs);
|
||||
int ret = qcow2_flush_caches(bs);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
@ -541,8 +542,9 @@ int qcow2_mark_consistent(BlockDriverState *bs)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int qcow2_check(BlockDriverState *bs, BdrvCheckResult *result,
|
||||
BdrvCheckMode fix)
|
||||
static int coroutine_fn qcow2_co_check_locked(BlockDriverState *bs,
|
||||
BdrvCheckResult *result,
|
||||
BdrvCheckMode fix)
|
||||
{
|
||||
int ret = qcow2_check_refcounts(bs, result, fix);
|
||||
if (ret < 0) {
|
||||
|
@ -559,26 +561,36 @@ static int qcow2_check(BlockDriverState *bs, BdrvCheckResult *result,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int validate_table_offset(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t entries, size_t entry_len)
|
||||
static int coroutine_fn qcow2_co_check(BlockDriverState *bs,
|
||||
BdrvCheckResult *result,
|
||||
BdrvCheckMode fix)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
uint64_t size;
|
||||
int ret;
|
||||
|
||||
qemu_co_mutex_lock(&s->lock);
|
||||
ret = qcow2_co_check_locked(bs, result, fix);
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int qcow2_validate_table(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t entries, size_t entry_len,
|
||||
int64_t max_size_bytes, const char *table_name,
|
||||
Error **errp)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
|
||||
if (entries > max_size_bytes / entry_len) {
|
||||
error_setg(errp, "%s too large", table_name);
|
||||
return -EFBIG;
|
||||
}
|
||||
|
||||
/* Use signed INT64_MAX as the maximum even for uint64_t header fields,
|
||||
* because values will be passed to qemu functions taking int64_t. */
|
||||
if (entries > INT64_MAX / entry_len) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
size = entries * entry_len;
|
||||
|
||||
if (INT64_MAX - size < offset) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Tables must be cluster aligned */
|
||||
if (offset_into_cluster(s, offset) != 0) {
|
||||
if ((INT64_MAX - entries * entry_len < offset) ||
|
||||
(offset_into_cluster(s, offset) != 0)) {
|
||||
error_setg(errp, "%s offset invalid", table_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
@ -1118,8 +1130,9 @@ static int qcow2_update_options(BlockDriverState *bs, QDict *options,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp)
|
||||
/* Called with s->lock held. */
|
||||
static int coroutine_fn qcow2_do_open(BlockDriverState *bs, QDict *options,
|
||||
int flags, Error **errp)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
unsigned int len, i;
|
||||
|
@ -1308,47 +1321,42 @@ static int qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
|
|||
s->refcount_table_size =
|
||||
header.refcount_table_clusters << (s->cluster_bits - 3);
|
||||
|
||||
if (header.refcount_table_clusters > qcow2_max_refcount_clusters(s)) {
|
||||
error_setg(errp, "Reference count table too large");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (header.refcount_table_clusters == 0 && !(flags & BDRV_O_CHECK)) {
|
||||
error_setg(errp, "Image does not contain a reference count table");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = validate_table_offset(bs, s->refcount_table_offset,
|
||||
s->refcount_table_size, sizeof(uint64_t));
|
||||
ret = qcow2_validate_table(bs, s->refcount_table_offset,
|
||||
header.refcount_table_clusters,
|
||||
s->cluster_size, QCOW_MAX_REFTABLE_SIZE,
|
||||
"Reference count table", errp);
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "Invalid reference count table offset");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Snapshot table offset/length */
|
||||
if (header.nb_snapshots > QCOW_MAX_SNAPSHOTS) {
|
||||
error_setg(errp, "Too many snapshots");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = validate_table_offset(bs, header.snapshots_offset,
|
||||
header.nb_snapshots,
|
||||
sizeof(QCowSnapshotHeader));
|
||||
/* The total size in bytes of the snapshot table is checked in
|
||||
* qcow2_read_snapshots() because the size of each snapshot is
|
||||
* variable and we don't know it yet.
|
||||
* Here we only check the offset and number of snapshots. */
|
||||
ret = qcow2_validate_table(bs, header.snapshots_offset,
|
||||
header.nb_snapshots,
|
||||
sizeof(QCowSnapshotHeader),
|
||||
sizeof(QCowSnapshotHeader) * QCOW_MAX_SNAPSHOTS,
|
||||
"Snapshot table", errp);
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "Invalid snapshot table offset");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* read the level 1 table */
|
||||
if (header.l1_size > QCOW_MAX_L1_SIZE / sizeof(uint64_t)) {
|
||||
error_setg(errp, "Active L1 table too large");
|
||||
ret = -EFBIG;
|
||||
ret = qcow2_validate_table(bs, header.l1_table_offset,
|
||||
header.l1_size, sizeof(uint64_t),
|
||||
QCOW_MAX_L1_SIZE, "Active L1 table", errp);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
s->l1_size = header.l1_size;
|
||||
s->l1_table_offset = header.l1_table_offset;
|
||||
|
||||
l1_vm_state_index = size_to_l1(s, header.size);
|
||||
if (l1_vm_state_index > INT_MAX) {
|
||||
|
@ -1366,15 +1374,6 @@ static int qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
|
|||
goto fail;
|
||||
}
|
||||
|
||||
ret = validate_table_offset(bs, header.l1_table_offset,
|
||||
header.l1_size, sizeof(uint64_t));
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "Invalid L1 table offset");
|
||||
goto fail;
|
||||
}
|
||||
s->l1_table_offset = header.l1_table_offset;
|
||||
|
||||
|
||||
if (s->l1_size > 0) {
|
||||
s->l1_table = qemu_try_blockalign(bs->file->bs,
|
||||
ROUND_UP(s->l1_size * sizeof(uint64_t), 512));
|
||||
|
@ -1498,8 +1497,6 @@ static int qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
|
|||
}
|
||||
}
|
||||
|
||||
/* Initialise locks */
|
||||
qemu_co_mutex_init(&s->lock);
|
||||
bs->supported_zero_flags = header.version >= 3 ? BDRV_REQ_MAY_UNMAP : 0;
|
||||
|
||||
/* Repair image if dirty */
|
||||
|
@ -1507,7 +1504,8 @@ static int qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
|
|||
(s->incompatible_features & QCOW2_INCOMPAT_DIRTY)) {
|
||||
BdrvCheckResult result = {0};
|
||||
|
||||
ret = qcow2_check(bs, &result, BDRV_FIX_ERRORS | BDRV_FIX_LEAKS);
|
||||
ret = qcow2_co_check_locked(bs, &result,
|
||||
BDRV_FIX_ERRORS | BDRV_FIX_LEAKS);
|
||||
if (ret < 0 || result.check_errors) {
|
||||
if (ret >= 0) {
|
||||
ret = -EIO;
|
||||
|
@ -1545,16 +1543,53 @@ static int qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
|
|||
return ret;
|
||||
}
|
||||
|
||||
typedef struct QCow2OpenCo {
|
||||
BlockDriverState *bs;
|
||||
QDict *options;
|
||||
int flags;
|
||||
Error **errp;
|
||||
int ret;
|
||||
} QCow2OpenCo;
|
||||
|
||||
static void coroutine_fn qcow2_open_entry(void *opaque)
|
||||
{
|
||||
QCow2OpenCo *qoc = opaque;
|
||||
BDRVQcow2State *s = qoc->bs->opaque;
|
||||
|
||||
qemu_co_mutex_lock(&s->lock);
|
||||
qoc->ret = qcow2_do_open(qoc->bs, qoc->options, qoc->flags, qoc->errp);
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
}
|
||||
|
||||
static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
QCow2OpenCo qoc = {
|
||||
.bs = bs,
|
||||
.options = options,
|
||||
.flags = flags,
|
||||
.errp = errp,
|
||||
.ret = -EINPROGRESS
|
||||
};
|
||||
|
||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
|
||||
false, errp);
|
||||
if (!bs->file) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return qcow2_do_open(bs, options, flags, errp);
|
||||
/* Initialise locks */
|
||||
qemu_co_mutex_init(&s->lock);
|
||||
|
||||
if (qemu_in_coroutine()) {
|
||||
/* From bdrv_co_create. */
|
||||
qcow2_open_entry(&qoc);
|
||||
} else {
|
||||
qemu_coroutine_enter(qemu_coroutine_create(qcow2_open_entry, &qoc));
|
||||
BDRV_POLL_WHILE(bs, qoc.ret == -EINPROGRESS);
|
||||
}
|
||||
return qoc.ret;
|
||||
}
|
||||
|
||||
static void qcow2_refresh_limits(BlockDriverState *bs, Error **errp)
|
||||
|
@ -2106,7 +2141,8 @@ static void qcow2_close(BlockDriverState *bs)
|
|||
qcow2_free_snapshots(bs);
|
||||
}
|
||||
|
||||
static void qcow2_invalidate_cache(BlockDriverState *bs, Error **errp)
|
||||
static void coroutine_fn qcow2_co_invalidate_cache(BlockDriverState *bs,
|
||||
Error **errp)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
int flags = s->flags;
|
||||
|
@ -2129,7 +2165,9 @@ static void qcow2_invalidate_cache(BlockDriverState *bs, Error **errp)
|
|||
options = qdict_clone_shallow(bs->options);
|
||||
|
||||
flags &= ~BDRV_O_INACTIVE;
|
||||
qemu_co_mutex_lock(&s->lock);
|
||||
ret = qcow2_do_open(bs, options, flags, &local_err);
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
QDECREF(options);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
|
@ -2412,39 +2450,26 @@ static int qcow2_crypt_method_from_format(const char *encryptfmt)
|
|||
}
|
||||
}
|
||||
|
||||
static int qcow2_set_up_encryption(BlockDriverState *bs, const char *encryptfmt,
|
||||
QemuOpts *opts, Error **errp)
|
||||
static int qcow2_set_up_encryption(BlockDriverState *bs,
|
||||
QCryptoBlockCreateOptions *cryptoopts,
|
||||
Error **errp)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
QCryptoBlockCreateOptions *cryptoopts = NULL;
|
||||
QCryptoBlock *crypto = NULL;
|
||||
int ret = -EINVAL;
|
||||
QDict *options, *encryptopts;
|
||||
int fmt;
|
||||
int fmt, ret;
|
||||
|
||||
options = qemu_opts_to_qdict(opts, NULL);
|
||||
qdict_extract_subqdict(options, &encryptopts, "encrypt.");
|
||||
QDECREF(options);
|
||||
|
||||
fmt = qcow2_crypt_method_from_format(encryptfmt);
|
||||
|
||||
switch (fmt) {
|
||||
case QCOW_CRYPT_LUKS:
|
||||
cryptoopts = block_crypto_create_opts_init(
|
||||
Q_CRYPTO_BLOCK_FORMAT_LUKS, encryptopts, errp);
|
||||
switch (cryptoopts->format) {
|
||||
case Q_CRYPTO_BLOCK_FORMAT_LUKS:
|
||||
fmt = QCOW_CRYPT_LUKS;
|
||||
break;
|
||||
case QCOW_CRYPT_AES:
|
||||
cryptoopts = block_crypto_create_opts_init(
|
||||
Q_CRYPTO_BLOCK_FORMAT_QCOW, encryptopts, errp);
|
||||
case Q_CRYPTO_BLOCK_FORMAT_QCOW:
|
||||
fmt = QCOW_CRYPT_AES;
|
||||
break;
|
||||
default:
|
||||
error_setg(errp, "Unknown encryption format '%s'", encryptfmt);
|
||||
break;
|
||||
}
|
||||
if (!cryptoopts) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
error_setg(errp, "Crypto format not supported in qcow2");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
s->crypt_method_header = fmt;
|
||||
|
||||
crypto = qcrypto_block_create(cryptoopts, "encrypt.",
|
||||
|
@ -2452,8 +2477,7 @@ static int qcow2_set_up_encryption(BlockDriverState *bs, const char *encryptfmt,
|
|||
qcow2_crypto_hdr_write_func,
|
||||
bs, errp);
|
||||
if (!crypto) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = qcow2_update_header(bs);
|
||||
|
@ -2462,10 +2486,9 @@ static int qcow2_set_up_encryption(BlockDriverState *bs, const char *encryptfmt,
|
|||
goto out;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
QDECREF(encryptopts);
|
||||
qcrypto_block_free(crypto);
|
||||
qapi_free_QCryptoBlockCreateOptions(cryptoopts);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -2663,19 +2686,26 @@ static int64_t qcow2_calc_prealloc_size(int64_t total_size,
|
|||
return meta_size + aligned_total_size;
|
||||
}
|
||||
|
||||
static size_t qcow2_opt_get_cluster_size_del(QemuOpts *opts, Error **errp)
|
||||
static bool validate_cluster_size(size_t cluster_size, Error **errp)
|
||||
{
|
||||
size_t cluster_size;
|
||||
int cluster_bits;
|
||||
|
||||
cluster_size = qemu_opt_get_size_del(opts, BLOCK_OPT_CLUSTER_SIZE,
|
||||
DEFAULT_CLUSTER_SIZE);
|
||||
cluster_bits = ctz32(cluster_size);
|
||||
int cluster_bits = ctz32(cluster_size);
|
||||
if (cluster_bits < MIN_CLUSTER_BITS || cluster_bits > MAX_CLUSTER_BITS ||
|
||||
(1 << cluster_bits) != cluster_size)
|
||||
{
|
||||
error_setg(errp, "Cluster size must be a power of two between %d and "
|
||||
"%dk", 1 << MIN_CLUSTER_BITS, 1 << (MAX_CLUSTER_BITS - 10));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t qcow2_opt_get_cluster_size_del(QemuOpts *opts, Error **errp)
|
||||
{
|
||||
size_t cluster_size;
|
||||
|
||||
cluster_size = qemu_opt_get_size_del(opts, BLOCK_OPT_CLUSTER_SIZE,
|
||||
DEFAULT_CLUSTER_SIZE);
|
||||
if (!validate_cluster_size(cluster_size, errp)) {
|
||||
return 0;
|
||||
}
|
||||
return cluster_size;
|
||||
|
@ -2724,12 +2754,9 @@ static uint64_t qcow2_opt_get_refcount_bits_del(QemuOpts *opts, int version,
|
|||
}
|
||||
|
||||
static int coroutine_fn
|
||||
qcow2_co_create2(const char *filename, int64_t total_size,
|
||||
const char *backing_file, const char *backing_format,
|
||||
int flags, size_t cluster_size, PreallocMode prealloc,
|
||||
QemuOpts *opts, int version, int refcount_order,
|
||||
const char *encryptfmt, Error **errp)
|
||||
qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
|
||||
{
|
||||
BlockdevCreateOptionsQcow2 *qcow2_opts;
|
||||
QDict *options;
|
||||
|
||||
/*
|
||||
|
@ -2744,36 +2771,132 @@ qcow2_co_create2(const char *filename, int64_t total_size,
|
|||
* 2 GB for 64k clusters, and we don't want to have a 2 GB initial file
|
||||
* size for any qcow2 image.
|
||||
*/
|
||||
BlockBackend *blk;
|
||||
BlockBackend *blk = NULL;
|
||||
BlockDriverState *bs = NULL;
|
||||
QCowHeader *header;
|
||||
size_t cluster_size;
|
||||
int version;
|
||||
int refcount_order;
|
||||
uint64_t* refcount_table;
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
|
||||
if (prealloc == PREALLOC_MODE_FULL || prealloc == PREALLOC_MODE_FALLOC) {
|
||||
int64_t prealloc_size =
|
||||
qcow2_calc_prealloc_size(total_size, cluster_size, refcount_order);
|
||||
qemu_opt_set_number(opts, BLOCK_OPT_SIZE, prealloc_size, &error_abort);
|
||||
qemu_opt_set(opts, BLOCK_OPT_PREALLOC, PreallocMode_str(prealloc),
|
||||
&error_abort);
|
||||
}
|
||||
assert(create_options->driver == BLOCKDEV_DRIVER_QCOW2);
|
||||
qcow2_opts = &create_options->u.qcow2;
|
||||
|
||||
ret = bdrv_create_file(filename, opts, &local_err);
|
||||
if (ret < 0) {
|
||||
error_propagate(errp, local_err);
|
||||
return ret;
|
||||
}
|
||||
|
||||
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);
|
||||
bs = bdrv_open_blockdev_ref(qcow2_opts->file, errp);
|
||||
if (bs == NULL) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Validate options and set default values */
|
||||
if (!QEMU_IS_ALIGNED(qcow2_opts->size, BDRV_SECTOR_SIZE)) {
|
||||
error_setg(errp, "Image size must be a multiple of 512 bytes");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (qcow2_opts->has_version) {
|
||||
switch (qcow2_opts->version) {
|
||||
case BLOCKDEV_QCOW2_VERSION_V2:
|
||||
version = 2;
|
||||
break;
|
||||
case BLOCKDEV_QCOW2_VERSION_V3:
|
||||
version = 3;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
} else {
|
||||
version = 3;
|
||||
}
|
||||
|
||||
if (qcow2_opts->has_cluster_size) {
|
||||
cluster_size = qcow2_opts->cluster_size;
|
||||
} else {
|
||||
cluster_size = DEFAULT_CLUSTER_SIZE;
|
||||
}
|
||||
|
||||
if (!validate_cluster_size(cluster_size, errp)) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!qcow2_opts->has_preallocation) {
|
||||
qcow2_opts->preallocation = PREALLOC_MODE_OFF;
|
||||
}
|
||||
if (qcow2_opts->has_backing_file &&
|
||||
qcow2_opts->preallocation != PREALLOC_MODE_OFF)
|
||||
{
|
||||
error_setg(errp, "Backing file and preallocation cannot be used at "
|
||||
"the same time");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (qcow2_opts->has_backing_fmt && !qcow2_opts->has_backing_file) {
|
||||
error_setg(errp, "Backing format cannot be used without backing file");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!qcow2_opts->has_lazy_refcounts) {
|
||||
qcow2_opts->lazy_refcounts = false;
|
||||
}
|
||||
if (version < 3 && qcow2_opts->lazy_refcounts) {
|
||||
error_setg(errp, "Lazy refcounts only supported with compatibility "
|
||||
"level 1.1 and above (use version=v3 or greater)");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!qcow2_opts->has_refcount_bits) {
|
||||
qcow2_opts->refcount_bits = 16;
|
||||
}
|
||||
if (qcow2_opts->refcount_bits > 64 ||
|
||||
!is_power_of_2(qcow2_opts->refcount_bits))
|
||||
{
|
||||
error_setg(errp, "Refcount width must be a power of two and may not "
|
||||
"exceed 64 bits");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (version < 3 && qcow2_opts->refcount_bits != 16) {
|
||||
error_setg(errp, "Different refcount widths than 16 bits require "
|
||||
"compatibility level 1.1 or above (use version=v3 or "
|
||||
"greater)");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
refcount_order = ctz32(qcow2_opts->refcount_bits);
|
||||
|
||||
|
||||
/* Create BlockBackend to write to the image */
|
||||
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);
|
||||
|
||||
/* Clear the protocol layer and preallocate it if necessary */
|
||||
ret = blk_truncate(blk, 0, PREALLOC_MODE_OFF, errp);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (qcow2_opts->preallocation == PREALLOC_MODE_FULL ||
|
||||
qcow2_opts->preallocation == PREALLOC_MODE_FALLOC)
|
||||
{
|
||||
int64_t prealloc_size =
|
||||
qcow2_calc_prealloc_size(qcow2_opts->size, cluster_size,
|
||||
refcount_order);
|
||||
|
||||
ret = blk_truncate(blk, prealloc_size, qcow2_opts->preallocation, errp);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the header */
|
||||
QEMU_BUILD_BUG_ON((1 << MIN_CLUSTER_BITS) < sizeof(*header));
|
||||
header = g_malloc0(cluster_size);
|
||||
|
@ -2793,7 +2916,7 @@ qcow2_co_create2(const char *filename, int64_t total_size,
|
|||
/* We'll update this to correct value later */
|
||||
header->crypt_method = cpu_to_be32(QCOW_CRYPT_NONE);
|
||||
|
||||
if (flags & BLOCK_FLAG_LAZY_REFCOUNTS) {
|
||||
if (qcow2_opts->lazy_refcounts) {
|
||||
header->compatible_features |=
|
||||
cpu_to_be64(QCOW2_COMPAT_LAZY_REFCOUNTS);
|
||||
}
|
||||
|
@ -2826,7 +2949,8 @@ qcow2_co_create2(const char *filename, int64_t total_size,
|
|||
*/
|
||||
options = qdict_new();
|
||||
qdict_put_str(options, "driver", "qcow2");
|
||||
blk = blk_new_open(filename, NULL, options,
|
||||
qdict_put_str(options, "file", bs->node_name);
|
||||
blk = blk_new_open(NULL, NULL, options,
|
||||
BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_NO_FLUSH,
|
||||
&local_err);
|
||||
if (blk == NULL) {
|
||||
|
@ -2854,33 +2978,41 @@ qcow2_co_create2(const char *filename, int64_t total_size,
|
|||
}
|
||||
|
||||
/* Okay, now that we have a valid image, let's give it the right size */
|
||||
ret = blk_truncate(blk, total_size, PREALLOC_MODE_OFF, errp);
|
||||
ret = blk_truncate(blk, qcow2_opts->size, PREALLOC_MODE_OFF, errp);
|
||||
if (ret < 0) {
|
||||
error_prepend(errp, "Could not resize image: ");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Want a backing file? There you go.*/
|
||||
if (backing_file) {
|
||||
ret = bdrv_change_backing_file(blk_bs(blk), backing_file, backing_format);
|
||||
if (qcow2_opts->has_backing_file) {
|
||||
const char *backing_format = NULL;
|
||||
|
||||
if (qcow2_opts->has_backing_fmt) {
|
||||
backing_format = BlockdevDriver_str(qcow2_opts->backing_fmt);
|
||||
}
|
||||
|
||||
ret = bdrv_change_backing_file(blk_bs(blk), qcow2_opts->backing_file,
|
||||
backing_format);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "Could not assign backing file '%s' "
|
||||
"with format '%s'", backing_file, backing_format);
|
||||
"with format '%s'", qcow2_opts->backing_file,
|
||||
backing_format);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* Want encryption? There you go. */
|
||||
if (encryptfmt) {
|
||||
ret = qcow2_set_up_encryption(blk_bs(blk), encryptfmt, opts, errp);
|
||||
if (qcow2_opts->has_encrypt) {
|
||||
ret = qcow2_set_up_encryption(blk_bs(blk), qcow2_opts->encrypt, errp);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* And if we're supposed to preallocate metadata, do that now */
|
||||
if (prealloc != PREALLOC_MODE_OFF) {
|
||||
ret = preallocate(blk_bs(blk), 0, total_size);
|
||||
if (qcow2_opts->preallocation != PREALLOC_MODE_OFF) {
|
||||
ret = preallocate(blk_bs(blk), 0, qcow2_opts->size);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "Could not preallocate metadata");
|
||||
goto out;
|
||||
|
@ -2898,7 +3030,8 @@ qcow2_co_create2(const char *filename, int64_t total_size,
|
|||
*/
|
||||
options = qdict_new();
|
||||
qdict_put_str(options, "driver", "qcow2");
|
||||
blk = blk_new_open(filename, NULL, options,
|
||||
qdict_put_str(options, "file", bs->node_name);
|
||||
blk = blk_new_open(NULL, NULL, options,
|
||||
BDRV_O_RDWR | BDRV_O_NO_BACKING | BDRV_O_NO_IO,
|
||||
&local_err);
|
||||
if (blk == NULL) {
|
||||
|
@ -2909,104 +3042,120 @@ qcow2_co_create2(const char *filename, int64_t total_size,
|
|||
|
||||
ret = 0;
|
||||
out:
|
||||
if (blk) {
|
||||
blk_unref(blk);
|
||||
}
|
||||
blk_unref(blk);
|
||||
bdrv_unref(bs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int coroutine_fn qcow2_co_create_opts(const char *filename, QemuOpts *opts,
|
||||
Error **errp)
|
||||
{
|
||||
char *backing_file = NULL;
|
||||
char *backing_fmt = NULL;
|
||||
char *buf = NULL;
|
||||
uint64_t size = 0;
|
||||
int flags = 0;
|
||||
size_t cluster_size = DEFAULT_CLUSTER_SIZE;
|
||||
PreallocMode prealloc;
|
||||
int version;
|
||||
uint64_t refcount_bits;
|
||||
int refcount_order;
|
||||
char *encryptfmt = NULL;
|
||||
BlockdevCreateOptions *create_options = NULL;
|
||||
QDict *qdict = NULL;
|
||||
QObject *qobj;
|
||||
Visitor *v;
|
||||
BlockDriverState *bs = NULL;
|
||||
Error *local_err = NULL;
|
||||
const char *val;
|
||||
int ret;
|
||||
|
||||
/* Read out options */
|
||||
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);
|
||||
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 finish;
|
||||
}
|
||||
} else if (qemu_opt_get_bool_del(opts, BLOCK_OPT_ENCRYPT, false)) {
|
||||
encryptfmt = g_strdup("aes");
|
||||
/* Only the keyval visitor supports the dotted syntax needed for
|
||||
* encryption, so go through a QDict before getting a QAPI type. Ignore
|
||||
* options meant for the protocol layer so that the visitor doesn't
|
||||
* complain. */
|
||||
qdict = qemu_opts_to_qdict_filtered(opts, NULL, bdrv_qcow2.create_opts,
|
||||
true);
|
||||
|
||||
/* Handle encryption options */
|
||||
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);
|
||||
}
|
||||
cluster_size = qcow2_opt_get_cluster_size_del(opts, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
|
||||
val = qdict_get_try_str(qdict, BLOCK_OPT_ENCRYPT_FORMAT);
|
||||
if (val && !strcmp(val, "aes")) {
|
||||
qdict_put_str(qdict, BLOCK_OPT_ENCRYPT_FORMAT, "qcow");
|
||||
}
|
||||
|
||||
/* Convert compat=0.10/1.1 into compat=v2/v3, to be renamed into
|
||||
* version=v2/v3 below. */
|
||||
val = qdict_get_try_str(qdict, BLOCK_OPT_COMPAT_LEVEL);
|
||||
if (val && !strcmp(val, "0.10")) {
|
||||
qdict_put_str(qdict, BLOCK_OPT_COMPAT_LEVEL, "v2");
|
||||
} else if (val && !strcmp(val, "1.1")) {
|
||||
qdict_put_str(qdict, BLOCK_OPT_COMPAT_LEVEL, "v3");
|
||||
}
|
||||
|
||||
/* Change legacy command line options into QMP ones */
|
||||
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_LAZY_REFCOUNTS, "lazy-refcounts" },
|
||||
{ BLOCK_OPT_REFCOUNT_BITS, "refcount-bits" },
|
||||
{ BLOCK_OPT_ENCRYPT, BLOCK_OPT_ENCRYPT_FORMAT },
|
||||
{ BLOCK_OPT_COMPAT_LEVEL, "version" },
|
||||
{ NULL, NULL },
|
||||
};
|
||||
|
||||
if (!qdict_rename_keys(qdict, opt_renames, errp)) {
|
||||
ret = -EINVAL;
|
||||
goto finish;
|
||||
}
|
||||
buf = qemu_opt_get_del(opts, BLOCK_OPT_PREALLOC);
|
||||
prealloc = qapi_enum_parse(&PreallocMode_lookup, buf,
|
||||
PREALLOC_MODE_OFF, &local_err);
|
||||
|
||||
/* Create and open the file (protocol layer) */
|
||||
ret = bdrv_create_file(filename, opts, errp);
|
||||
if (ret < 0) {
|
||||
goto finish;
|
||||
}
|
||||
|
||||
bs = bdrv_open(filename, NULL, NULL,
|
||||
BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, errp);
|
||||
if (bs == NULL) {
|
||||
ret = -EIO;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* Set 'driver' and 'node' options */
|
||||
qdict_put_str(qdict, "driver", "qcow2");
|
||||
qdict_put_str(qdict, "file", bs->node_name);
|
||||
|
||||
/* Now get the QAPI type BlockdevCreateOptions */
|
||||
qobj = qdict_crumple(qdict, errp);
|
||||
QDECREF(qdict);
|
||||
qdict = qobject_to_qdict(qobj);
|
||||
if (qdict == NULL) {
|
||||
ret = -EINVAL;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
visit_type_BlockdevCreateOptions(v, NULL, &create_options, &local_err);
|
||||
visit_free(v);
|
||||
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
ret = -EINVAL;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
version = qcow2_opt_get_version_del(opts, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
ret = -EINVAL;
|
||||
/* Silently round up size */
|
||||
create_options->u.qcow2.size = ROUND_UP(create_options->u.qcow2.size,
|
||||
BDRV_SECTOR_SIZE);
|
||||
|
||||
/* Create the qcow2 image (format layer) */
|
||||
ret = qcow2_co_create(create_options, errp);
|
||||
if (ret < 0) {
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (qemu_opt_get_bool_del(opts, BLOCK_OPT_LAZY_REFCOUNTS, false)) {
|
||||
flags |= BLOCK_FLAG_LAZY_REFCOUNTS;
|
||||
}
|
||||
|
||||
if (backing_file && prealloc != PREALLOC_MODE_OFF) {
|
||||
error_setg(errp, "Backing file and preallocation cannot be used at "
|
||||
"the same time");
|
||||
ret = -EINVAL;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (version < 3 && (flags & BLOCK_FLAG_LAZY_REFCOUNTS)) {
|
||||
error_setg(errp, "Lazy refcounts only supported with compatibility "
|
||||
"level 1.1 and above (use compat=1.1 or greater)");
|
||||
ret = -EINVAL;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
refcount_bits = qcow2_opt_get_refcount_bits_del(opts, version, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
ret = -EINVAL;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
refcount_order = ctz32(refcount_bits);
|
||||
|
||||
ret = qcow2_co_create2(filename, size, backing_file, backing_fmt, flags,
|
||||
cluster_size, prealloc, opts, version, refcount_order,
|
||||
encryptfmt, &local_err);
|
||||
error_propagate(errp, local_err);
|
||||
|
||||
ret = 0;
|
||||
finish:
|
||||
g_free(backing_file);
|
||||
g_free(backing_fmt);
|
||||
g_free(encryptfmt);
|
||||
g_free(buf);
|
||||
QDECREF(qdict);
|
||||
bdrv_unref(bs);
|
||||
qapi_free_BlockdevCreateOptions(create_options);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -3647,22 +3796,10 @@ static coroutine_fn int qcow2_co_flush_to_os(BlockDriverState *bs)
|
|||
int ret;
|
||||
|
||||
qemu_co_mutex_lock(&s->lock);
|
||||
ret = qcow2_cache_write(bs, s->l2_table_cache);
|
||||
if (ret < 0) {
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (qcow2_need_accurate_refcounts(s)) {
|
||||
ret = qcow2_cache_write(bs, s->refcount_block_cache);
|
||||
if (ret < 0) {
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
ret = qcow2_write_caches(bs);
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static BlockMeasureInfo *qcow2_measure(QemuOpts *opts, BlockDriverState *in_bs,
|
||||
|
@ -4353,6 +4490,7 @@ BlockDriver bdrv_qcow2 = {
|
|||
.bdrv_join_options = qcow2_join_options,
|
||||
.bdrv_child_perm = bdrv_format_default_perms,
|
||||
.bdrv_co_create_opts = qcow2_co_create_opts,
|
||||
.bdrv_co_create = qcow2_co_create,
|
||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
||||
.bdrv_co_block_status = qcow2_co_block_status,
|
||||
|
||||
|
@ -4382,11 +4520,11 @@ BlockDriver bdrv_qcow2 = {
|
|||
.bdrv_change_backing_file = qcow2_change_backing_file,
|
||||
|
||||
.bdrv_refresh_limits = qcow2_refresh_limits,
|
||||
.bdrv_invalidate_cache = qcow2_invalidate_cache,
|
||||
.bdrv_co_invalidate_cache = qcow2_co_invalidate_cache,
|
||||
.bdrv_inactivate = qcow2_inactivate,
|
||||
|
||||
.create_opts = &qcow2_create_opts,
|
||||
.bdrv_check = qcow2_check,
|
||||
.bdrv_co_check = qcow2_co_check,
|
||||
.bdrv_amend_options = qcow2_amend_options,
|
||||
|
||||
.bdrv_detach_aio_context = qcow2_detach_aio_context,
|
||||
|
|
|
@ -485,11 +485,6 @@ static inline int64_t qcow2_vm_state_offset(BDRVQcow2State *s)
|
|||
return (int64_t)s->l1_vm_state_index << (s->cluster_bits + s->l2_bits);
|
||||
}
|
||||
|
||||
static inline uint64_t qcow2_max_refcount_clusters(BDRVQcow2State *s)
|
||||
{
|
||||
return QCOW_MAX_REFTABLE_SIZE >> s->cluster_bits;
|
||||
}
|
||||
|
||||
static inline QCow2ClusterType qcow2_get_cluster_type(uint64_t l2_entry)
|
||||
{
|
||||
if (l2_entry & QCOW_OFLAG_COMPRESSED) {
|
||||
|
@ -547,6 +542,11 @@ void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset,
|
|||
int64_t size, const char *message_format, ...)
|
||||
GCC_FMT_ATTR(5, 6);
|
||||
|
||||
int qcow2_validate_table(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t entries, size_t entry_len,
|
||||
int64_t max_size_bytes, const char *table_name,
|
||||
Error **errp);
|
||||
|
||||
/* qcow2-refcount.c functions */
|
||||
int qcow2_refcount_init(BlockDriverState *bs);
|
||||
void qcow2_refcount_close(BlockDriverState *bs);
|
||||
|
@ -576,6 +576,8 @@ void qcow2_free_any_clusters(BlockDriverState *bs, uint64_t l2_entry,
|
|||
int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||
int64_t l1_table_offset, int l1_size, int addend);
|
||||
|
||||
int coroutine_fn qcow2_flush_caches(BlockDriverState *bs);
|
||||
int coroutine_fn qcow2_write_caches(BlockDriverState *bs);
|
||||
int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
BdrvCheckMode fix);
|
||||
|
||||
|
|
|
@ -217,6 +217,7 @@ static void qed_check_mark_clean(BDRVQEDState *s, BdrvCheckResult *result)
|
|||
qed_write_header_sync(s);
|
||||
}
|
||||
|
||||
/* Called with table_lock held. */
|
||||
int qed_check(BDRVQEDState *s, BdrvCheckResult *result, bool fix)
|
||||
{
|
||||
QEDCheck check = {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include "qed.h"
|
||||
#include "qemu/bswap.h"
|
||||
|
||||
/* Called either from qed_check or with table_lock held. */
|
||||
/* Called with table_lock held. */
|
||||
static int qed_read_table(BDRVQEDState *s, uint64_t offset, QEDTable *table)
|
||||
{
|
||||
QEMUIOVector qiov;
|
||||
|
@ -33,13 +33,9 @@ static int qed_read_table(BDRVQEDState *s, uint64_t offset, QEDTable *table)
|
|||
|
||||
trace_qed_read_table(s, offset, table);
|
||||
|
||||
if (qemu_in_coroutine()) {
|
||||
qemu_co_mutex_unlock(&s->table_lock);
|
||||
}
|
||||
qemu_co_mutex_unlock(&s->table_lock);
|
||||
ret = bdrv_preadv(s->bs->file, offset, &qiov);
|
||||
if (qemu_in_coroutine()) {
|
||||
qemu_co_mutex_lock(&s->table_lock);
|
||||
}
|
||||
qemu_co_mutex_lock(&s->table_lock);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
@ -67,7 +63,7 @@ out:
|
|||
* @n: Number of elements
|
||||
* @flush: Whether or not to sync to disk
|
||||
*
|
||||
* Called either from qed_check or with table_lock held.
|
||||
* Called with table_lock held.
|
||||
*/
|
||||
static int qed_write_table(BDRVQEDState *s, uint64_t offset, QEDTable *table,
|
||||
unsigned int index, unsigned int n, bool flush)
|
||||
|
@ -104,13 +100,9 @@ static int qed_write_table(BDRVQEDState *s, uint64_t offset, QEDTable *table,
|
|||
/* Adjust for offset into table */
|
||||
offset += start * sizeof(uint64_t);
|
||||
|
||||
if (qemu_in_coroutine()) {
|
||||
qemu_co_mutex_unlock(&s->table_lock);
|
||||
}
|
||||
qemu_co_mutex_unlock(&s->table_lock);
|
||||
ret = bdrv_pwritev(s->bs->file, offset, &qiov);
|
||||
if (qemu_in_coroutine()) {
|
||||
qemu_co_mutex_lock(&s->table_lock);
|
||||
}
|
||||
qemu_co_mutex_lock(&s->table_lock);
|
||||
trace_qed_write_table_cb(s, table, flush, ret);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
|
@ -134,7 +126,7 @@ int qed_read_l1_table_sync(BDRVQEDState *s)
|
|||
return qed_read_table(s, s->header.l1_table_offset, s->l1_table);
|
||||
}
|
||||
|
||||
/* Called either from qed_check or with table_lock held. */
|
||||
/* Called with table_lock held. */
|
||||
int qed_write_l1_table(BDRVQEDState *s, unsigned int index, unsigned int n)
|
||||
{
|
||||
BLKDBG_EVENT(s->bs->file, BLKDBG_L1_UPDATE);
|
||||
|
@ -148,7 +140,7 @@ int qed_write_l1_table_sync(BDRVQEDState *s, unsigned int index,
|
|||
return qed_write_l1_table(s, index, n);
|
||||
}
|
||||
|
||||
/* Called either from qed_check or with table_lock held. */
|
||||
/* Called with table_lock held. */
|
||||
int qed_read_l2_table(BDRVQEDState *s, QEDRequest *request, uint64_t offset)
|
||||
{
|
||||
int ret;
|
||||
|
@ -191,7 +183,7 @@ int qed_read_l2_table_sync(BDRVQEDState *s, QEDRequest *request, uint64_t offset
|
|||
return qed_read_l2_table(s, request, offset);
|
||||
}
|
||||
|
||||
/* Called either from qed_check or with table_lock held. */
|
||||
/* Called with table_lock held. */
|
||||
int qed_write_l2_table(BDRVQEDState *s, QEDRequest *request,
|
||||
unsigned int index, unsigned int n, bool flush)
|
||||
{
|
||||
|
|
66
block/qed.c
66
block/qed.c
|
@ -381,8 +381,9 @@ static void bdrv_qed_init_state(BlockDriverState *bs)
|
|||
qemu_co_queue_init(&s->allocating_write_reqs);
|
||||
}
|
||||
|
||||
static int bdrv_qed_do_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp)
|
||||
/* Called with table_lock held. */
|
||||
static int coroutine_fn bdrv_qed_do_open(BlockDriverState *bs, QDict *options,
|
||||
int flags, Error **errp)
|
||||
{
|
||||
BDRVQEDState *s = bs->opaque;
|
||||
QEDHeader le_header;
|
||||
|
@ -513,9 +514,35 @@ out:
|
|||
return ret;
|
||||
}
|
||||
|
||||
typedef struct QEDOpenCo {
|
||||
BlockDriverState *bs;
|
||||
QDict *options;
|
||||
int flags;
|
||||
Error **errp;
|
||||
int ret;
|
||||
} QEDOpenCo;
|
||||
|
||||
static void coroutine_fn bdrv_qed_open_entry(void *opaque)
|
||||
{
|
||||
QEDOpenCo *qoc = opaque;
|
||||
BDRVQEDState *s = qoc->bs->opaque;
|
||||
|
||||
qemu_co_mutex_lock(&s->table_lock);
|
||||
qoc->ret = bdrv_qed_do_open(qoc->bs, qoc->options, qoc->flags, qoc->errp);
|
||||
qemu_co_mutex_unlock(&s->table_lock);
|
||||
}
|
||||
|
||||
static int bdrv_qed_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp)
|
||||
{
|
||||
QEDOpenCo qoc = {
|
||||
.bs = bs,
|
||||
.options = options,
|
||||
.flags = flags,
|
||||
.errp = errp,
|
||||
.ret = -EINPROGRESS
|
||||
};
|
||||
|
||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
|
||||
false, errp);
|
||||
if (!bs->file) {
|
||||
|
@ -523,7 +550,14 @@ static int bdrv_qed_open(BlockDriverState *bs, QDict *options, int flags,
|
|||
}
|
||||
|
||||
bdrv_qed_init_state(bs);
|
||||
return bdrv_qed_do_open(bs, options, flags, errp);
|
||||
if (qemu_in_coroutine()) {
|
||||
bdrv_qed_open_entry(&qoc);
|
||||
} else {
|
||||
qemu_coroutine_enter(qemu_coroutine_create(bdrv_qed_open_entry, &qoc));
|
||||
BDRV_POLL_WHILE(bs, qoc.ret == -EINPROGRESS);
|
||||
}
|
||||
BDRV_POLL_WHILE(bs, qoc.ret == -EINPROGRESS);
|
||||
return qoc.ret;
|
||||
}
|
||||
|
||||
static void bdrv_qed_refresh_limits(BlockDriverState *bs, Error **errp)
|
||||
|
@ -1487,7 +1521,8 @@ static int bdrv_qed_change_backing_file(BlockDriverState *bs,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void bdrv_qed_invalidate_cache(BlockDriverState *bs, Error **errp)
|
||||
static void coroutine_fn bdrv_qed_co_invalidate_cache(BlockDriverState *bs,
|
||||
Error **errp)
|
||||
{
|
||||
BDRVQEDState *s = bs->opaque;
|
||||
Error *local_err = NULL;
|
||||
|
@ -1496,13 +1531,9 @@ static void bdrv_qed_invalidate_cache(BlockDriverState *bs, Error **errp)
|
|||
bdrv_qed_close(bs);
|
||||
|
||||
bdrv_qed_init_state(bs);
|
||||
if (qemu_in_coroutine()) {
|
||||
qemu_co_mutex_lock(&s->table_lock);
|
||||
}
|
||||
qemu_co_mutex_lock(&s->table_lock);
|
||||
ret = bdrv_qed_do_open(bs, NULL, bs->open_flags, &local_err);
|
||||
if (qemu_in_coroutine()) {
|
||||
qemu_co_mutex_unlock(&s->table_lock);
|
||||
}
|
||||
qemu_co_mutex_unlock(&s->table_lock);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
error_prepend(errp, "Could not reopen qed layer: ");
|
||||
|
@ -1513,12 +1544,17 @@ static void bdrv_qed_invalidate_cache(BlockDriverState *bs, Error **errp)
|
|||
}
|
||||
}
|
||||
|
||||
static int bdrv_qed_check(BlockDriverState *bs, BdrvCheckResult *result,
|
||||
BdrvCheckMode fix)
|
||||
static int bdrv_qed_co_check(BlockDriverState *bs, BdrvCheckResult *result,
|
||||
BdrvCheckMode fix)
|
||||
{
|
||||
BDRVQEDState *s = bs->opaque;
|
||||
int ret;
|
||||
|
||||
return qed_check(s, result, !!fix);
|
||||
qemu_co_mutex_lock(&s->table_lock);
|
||||
ret = qed_check(s, result, !!fix);
|
||||
qemu_co_mutex_unlock(&s->table_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static QemuOptsList qed_create_opts = {
|
||||
|
@ -1577,8 +1613,8 @@ static BlockDriver bdrv_qed = {
|
|||
.bdrv_get_info = bdrv_qed_get_info,
|
||||
.bdrv_refresh_limits = bdrv_qed_refresh_limits,
|
||||
.bdrv_change_backing_file = bdrv_qed_change_backing_file,
|
||||
.bdrv_invalidate_cache = bdrv_qed_invalidate_cache,
|
||||
.bdrv_check = bdrv_qed_check,
|
||||
.bdrv_co_invalidate_cache = bdrv_qed_co_invalidate_cache,
|
||||
.bdrv_co_check = bdrv_qed_co_check,
|
||||
.bdrv_detach_aio_context = bdrv_qed_detach_aio_context,
|
||||
.bdrv_attach_aio_context = bdrv_qed_attach_aio_context,
|
||||
.bdrv_co_drain_begin = bdrv_qed_co_drain_begin,
|
||||
|
|
405
block/rbd.c
405
block/rbd.c
|
@ -24,6 +24,8 @@
|
|||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
#include "qapi/qmp/qlist.h"
|
||||
#include "qapi/qobject-input-visitor.h"
|
||||
#include "qapi/qapi-visit-block-core.h"
|
||||
|
||||
/*
|
||||
* When specifying the image filename use:
|
||||
|
@ -101,6 +103,11 @@ typedef struct BDRVRBDState {
|
|||
char *snap;
|
||||
} BDRVRBDState;
|
||||
|
||||
static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx,
|
||||
BlockdevOptionsRbd *opts, bool cache,
|
||||
const char *keypairs, const char *secretid,
|
||||
Error **errp);
|
||||
|
||||
static char *qemu_rbd_next_tok(char *src, char delim, char **p)
|
||||
{
|
||||
char *end;
|
||||
|
@ -268,13 +275,14 @@ static int qemu_rbd_set_keypairs(rados_t cluster, const char *keypairs_json,
|
|||
key = qstring_get_str(name);
|
||||
|
||||
ret = rados_conf_set(cluster, key, qstring_get_str(value));
|
||||
QDECREF(name);
|
||||
QDECREF(value);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "invalid conf option %s", key);
|
||||
QDECREF(name);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
QDECREF(name);
|
||||
}
|
||||
|
||||
QDECREF(keypairs);
|
||||
|
@ -325,66 +333,92 @@ static QemuOptsList runtime_opts = {
|
|||
/*
|
||||
* server.* extracted manually, see qemu_rbd_mon_host()
|
||||
*/
|
||||
{
|
||||
.name = "password-secret",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "ID of secret providing the password",
|
||||
},
|
||||
|
||||
/*
|
||||
* Keys for qemu_rbd_parse_filename(), not in the QAPI schema
|
||||
*/
|
||||
{
|
||||
/*
|
||||
* HACK: name starts with '=' so that qemu_opts_parse()
|
||||
* can't set it
|
||||
*/
|
||||
.name = "=keyvalue-pairs",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Legacy rados key/value option parameters",
|
||||
},
|
||||
{
|
||||
.name = "filename",
|
||||
.type = QEMU_OPT_STRING,
|
||||
},
|
||||
{ /* end of list */ }
|
||||
},
|
||||
};
|
||||
|
||||
/* FIXME Deprecate and remove keypairs or make it available in QMP.
|
||||
* password_secret should eventually be configurable in opts->location. Support
|
||||
* for it in .bdrv_open will make it work here as well. */
|
||||
static int qemu_rbd_do_create(BlockdevCreateOptions *options,
|
||||
const char *keypairs, const char *password_secret,
|
||||
Error **errp)
|
||||
{
|
||||
BlockdevCreateOptionsRbd *opts = &options->u.rbd;
|
||||
rados_t cluster;
|
||||
rados_ioctx_t io_ctx;
|
||||
int obj_order = 0;
|
||||
int ret;
|
||||
|
||||
assert(options->driver == BLOCKDEV_DRIVER_RBD);
|
||||
if (opts->location->has_snapshot) {
|
||||
error_setg(errp, "Can't use snapshot name for image creation");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (opts->has_cluster_size) {
|
||||
int64_t objsize = opts->cluster_size;
|
||||
if ((objsize - 1) & objsize) { /* not a power of 2? */
|
||||
error_setg(errp, "obj size needs to be power of 2");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (objsize < 4096) {
|
||||
error_setg(errp, "obj size too small");
|
||||
return -EINVAL;
|
||||
}
|
||||
obj_order = ctz32(objsize);
|
||||
}
|
||||
|
||||
ret = qemu_rbd_connect(&cluster, &io_ctx, opts->location, false, keypairs,
|
||||
password_secret, errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = rbd_create(io_ctx, opts->location->image, opts->size, &obj_order);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "error rbd create");
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
rados_ioctx_destroy(io_ctx);
|
||||
rados_shutdown(cluster);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qemu_rbd_co_create(BlockdevCreateOptions *options, Error **errp)
|
||||
{
|
||||
return qemu_rbd_do_create(options, NULL, NULL, errp);
|
||||
}
|
||||
|
||||
static int coroutine_fn qemu_rbd_co_create_opts(const char *filename,
|
||||
QemuOpts *opts,
|
||||
Error **errp)
|
||||
{
|
||||
BlockdevCreateOptions *create_options;
|
||||
BlockdevCreateOptionsRbd *rbd_opts;
|
||||
BlockdevOptionsRbd *loc;
|
||||
Error *local_err = NULL;
|
||||
int64_t bytes = 0;
|
||||
int64_t objsize;
|
||||
int obj_order = 0;
|
||||
const char *pool, *image_name, *conf, *user, *keypairs;
|
||||
const char *secretid;
|
||||
rados_t cluster;
|
||||
rados_ioctx_t io_ctx;
|
||||
const char *keypairs, *password_secret;
|
||||
QDict *options = NULL;
|
||||
int ret = 0;
|
||||
|
||||
secretid = qemu_opt_get(opts, "password-secret");
|
||||
create_options = g_new0(BlockdevCreateOptions, 1);
|
||||
create_options->driver = BLOCKDEV_DRIVER_RBD;
|
||||
rbd_opts = &create_options->u.rbd;
|
||||
|
||||
rbd_opts->location = g_new0(BlockdevOptionsRbd, 1);
|
||||
|
||||
password_secret = qemu_opt_get(opts, "password-secret");
|
||||
|
||||
/* Read out options */
|
||||
bytes = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
||||
BDRV_SECTOR_SIZE);
|
||||
objsize = qemu_opt_get_size_del(opts, BLOCK_OPT_CLUSTER_SIZE, 0);
|
||||
if (objsize) {
|
||||
if ((objsize - 1) & objsize) { /* not a power of 2? */
|
||||
error_setg(errp, "obj size needs to be power of 2");
|
||||
ret = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
if (objsize < 4096) {
|
||||
error_setg(errp, "obj size too small");
|
||||
ret = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
obj_order = ctz32(objsize);
|
||||
}
|
||||
rbd_opts->size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
||||
BDRV_SECTOR_SIZE);
|
||||
rbd_opts->cluster_size = qemu_opt_get_size_del(opts,
|
||||
BLOCK_OPT_CLUSTER_SIZE, 0);
|
||||
rbd_opts->has_cluster_size = (rbd_opts->cluster_size != 0);
|
||||
|
||||
options = qdict_new();
|
||||
qemu_rbd_parse_filename(filename, options, &local_err);
|
||||
|
@ -400,61 +434,23 @@ static int coroutine_fn qemu_rbd_co_create_opts(const char *filename,
|
|||
* or blockdev_add, its members are typed according to the QAPI
|
||||
* schema, but when they come from -drive, they're all QString.
|
||||
*/
|
||||
pool = qdict_get_try_str(options, "pool");
|
||||
conf = qdict_get_try_str(options, "conf");
|
||||
user = qdict_get_try_str(options, "user");
|
||||
image_name = qdict_get_try_str(options, "image");
|
||||
keypairs = qdict_get_try_str(options, "=keyvalue-pairs");
|
||||
loc = rbd_opts->location;
|
||||
loc->pool = g_strdup(qdict_get_try_str(options, "pool"));
|
||||
loc->conf = g_strdup(qdict_get_try_str(options, "conf"));
|
||||
loc->has_conf = !!loc->conf;
|
||||
loc->user = g_strdup(qdict_get_try_str(options, "user"));
|
||||
loc->has_user = !!loc->user;
|
||||
loc->image = g_strdup(qdict_get_try_str(options, "image"));
|
||||
keypairs = qdict_get_try_str(options, "=keyvalue-pairs");
|
||||
|
||||
ret = rados_create(&cluster, user);
|
||||
ret = qemu_rbd_do_create(create_options, keypairs, password_secret, errp);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "error initializing");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* try default location when conf=NULL, but ignore failure */
|
||||
ret = rados_conf_read_file(cluster, conf);
|
||||
if (conf && ret < 0) {
|
||||
error_setg_errno(errp, -ret, "error reading conf file %s", conf);
|
||||
ret = -EIO;
|
||||
goto shutdown;
|
||||
}
|
||||
|
||||
ret = qemu_rbd_set_keypairs(cluster, keypairs, errp);
|
||||
if (ret < 0) {
|
||||
ret = -EIO;
|
||||
goto shutdown;
|
||||
}
|
||||
|
||||
if (qemu_rbd_set_auth(cluster, secretid, errp) < 0) {
|
||||
ret = -EIO;
|
||||
goto shutdown;
|
||||
}
|
||||
|
||||
ret = rados_connect(cluster);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "error connecting");
|
||||
goto shutdown;
|
||||
}
|
||||
|
||||
ret = rados_ioctx_create(cluster, pool, &io_ctx);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "error opening pool %s", pool);
|
||||
goto shutdown;
|
||||
}
|
||||
|
||||
ret = rbd_create(io_ctx, image_name, bytes, &obj_order);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "error rbd create");
|
||||
}
|
||||
|
||||
rados_ioctx_destroy(io_ctx);
|
||||
|
||||
shutdown:
|
||||
rados_shutdown(cluster);
|
||||
|
||||
exit:
|
||||
QDECREF(options);
|
||||
qapi_free_BlockdevCreateOptions(create_options);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -505,131 +501,83 @@ static void qemu_rbd_complete_aio(RADOSCB *rcb)
|
|||
qemu_aio_unref(acb);
|
||||
}
|
||||
|
||||
static char *qemu_rbd_mon_host(QDict *options, Error **errp)
|
||||
static char *qemu_rbd_mon_host(BlockdevOptionsRbd *opts, Error **errp)
|
||||
{
|
||||
const char **vals = g_new(const char *, qdict_size(options) + 1);
|
||||
char keybuf[32];
|
||||
const char **vals;
|
||||
const char *host, *port;
|
||||
char *rados_str;
|
||||
int i;
|
||||
InetSocketAddressBaseList *p;
|
||||
int i, cnt;
|
||||
|
||||
for (i = 0;; i++) {
|
||||
sprintf(keybuf, "server.%d.host", i);
|
||||
host = qdict_get_try_str(options, keybuf);
|
||||
qdict_del(options, keybuf);
|
||||
sprintf(keybuf, "server.%d.port", i);
|
||||
port = qdict_get_try_str(options, keybuf);
|
||||
qdict_del(options, keybuf);
|
||||
if (!host && !port) {
|
||||
break;
|
||||
}
|
||||
if (!host) {
|
||||
error_setg(errp, "Parameter server.%d.host is missing", i);
|
||||
rados_str = NULL;
|
||||
goto out;
|
||||
}
|
||||
if (!opts->has_server) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (cnt = 0, p = opts->server; p; p = p->next) {
|
||||
cnt++;
|
||||
}
|
||||
|
||||
vals = g_new(const char *, cnt + 1);
|
||||
|
||||
for (i = 0, p = opts->server; p; p = p->next, i++) {
|
||||
host = p->value->host;
|
||||
port = p->value->port;
|
||||
|
||||
if (strchr(host, ':')) {
|
||||
vals[i] = port ? g_strdup_printf("[%s]:%s", host, port)
|
||||
: g_strdup_printf("[%s]", host);
|
||||
vals[i] = g_strdup_printf("[%s]:%s", host, port);
|
||||
} else {
|
||||
vals[i] = port ? g_strdup_printf("%s:%s", host, port)
|
||||
: g_strdup(host);
|
||||
vals[i] = g_strdup_printf("%s:%s", host, port);
|
||||
}
|
||||
}
|
||||
vals[i] = NULL;
|
||||
|
||||
rados_str = i ? g_strjoinv(";", (char **)vals) : NULL;
|
||||
out:
|
||||
g_strfreev((char **)vals);
|
||||
return rados_str;
|
||||
}
|
||||
|
||||
static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp)
|
||||
static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx,
|
||||
BlockdevOptionsRbd *opts, bool cache,
|
||||
const char *keypairs, const char *secretid,
|
||||
Error **errp)
|
||||
{
|
||||
BDRVRBDState *s = bs->opaque;
|
||||
const char *pool, *snap, *conf, *user, *image_name, *keypairs;
|
||||
const char *secretid, *filename;
|
||||
QemuOpts *opts;
|
||||
Error *local_err = NULL;
|
||||
char *mon_host = NULL;
|
||||
Error *local_err = NULL;
|
||||
int r;
|
||||
|
||||
/* If we are given a filename, parse the filename, with precedence given to
|
||||
* filename encoded options */
|
||||
filename = qdict_get_try_str(options, "filename");
|
||||
if (filename) {
|
||||
warn_report("'filename' option specified. "
|
||||
"This is an unsupported option, and may be deprecated "
|
||||
"in the future");
|
||||
qemu_rbd_parse_filename(filename, options, &local_err);
|
||||
if (local_err) {
|
||||
r = -EINVAL;
|
||||
error_propagate(errp, local_err);
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||
mon_host = qemu_rbd_mon_host(opts, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
r = -EINVAL;
|
||||
goto failed_opts;
|
||||
}
|
||||
|
||||
mon_host = qemu_rbd_mon_host(options, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
r = -EINVAL;
|
||||
goto failed_opts;
|
||||
}
|
||||
|
||||
secretid = qemu_opt_get(opts, "password-secret");
|
||||
|
||||
pool = qemu_opt_get(opts, "pool");
|
||||
conf = qemu_opt_get(opts, "conf");
|
||||
snap = qemu_opt_get(opts, "snapshot");
|
||||
user = qemu_opt_get(opts, "user");
|
||||
image_name = qemu_opt_get(opts, "image");
|
||||
keypairs = qemu_opt_get(opts, "=keyvalue-pairs");
|
||||
|
||||
if (!pool || !image_name) {
|
||||
error_setg(errp, "Parameters 'pool' and 'image' are required");
|
||||
r = -EINVAL;
|
||||
goto failed_opts;
|
||||
}
|
||||
|
||||
r = rados_create(&s->cluster, user);
|
||||
r = rados_create(cluster, opts->user);
|
||||
if (r < 0) {
|
||||
error_setg_errno(errp, -r, "error initializing");
|
||||
goto failed_opts;
|
||||
}
|
||||
|
||||
s->snap = g_strdup(snap);
|
||||
s->image_name = g_strdup(image_name);
|
||||
|
||||
/* try default location when conf=NULL, but ignore failure */
|
||||
r = rados_conf_read_file(s->cluster, conf);
|
||||
if (conf && r < 0) {
|
||||
error_setg_errno(errp, -r, "error reading conf file %s", conf);
|
||||
r = rados_conf_read_file(*cluster, opts->conf);
|
||||
if (opts->has_conf && r < 0) {
|
||||
error_setg_errno(errp, -r, "error reading conf file %s", opts->conf);
|
||||
goto failed_shutdown;
|
||||
}
|
||||
|
||||
r = qemu_rbd_set_keypairs(s->cluster, keypairs, errp);
|
||||
r = qemu_rbd_set_keypairs(*cluster, keypairs, errp);
|
||||
if (r < 0) {
|
||||
goto failed_shutdown;
|
||||
}
|
||||
|
||||
if (mon_host) {
|
||||
r = rados_conf_set(s->cluster, "mon_host", mon_host);
|
||||
r = rados_conf_set(*cluster, "mon_host", mon_host);
|
||||
if (r < 0) {
|
||||
goto failed_shutdown;
|
||||
}
|
||||
}
|
||||
|
||||
if (qemu_rbd_set_auth(s->cluster, secretid, errp) < 0) {
|
||||
if (qemu_rbd_set_auth(*cluster, secretid, errp) < 0) {
|
||||
r = -EIO;
|
||||
goto failed_shutdown;
|
||||
}
|
||||
|
@ -641,24 +589,97 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
|
|||
* librbd defaults to no caching. If write through caching cannot
|
||||
* be set up, fall back to no caching.
|
||||
*/
|
||||
if (flags & BDRV_O_NOCACHE) {
|
||||
rados_conf_set(s->cluster, "rbd_cache", "false");
|
||||
if (cache) {
|
||||
rados_conf_set(*cluster, "rbd_cache", "true");
|
||||
} else {
|
||||
rados_conf_set(s->cluster, "rbd_cache", "true");
|
||||
rados_conf_set(*cluster, "rbd_cache", "false");
|
||||
}
|
||||
|
||||
r = rados_connect(s->cluster);
|
||||
r = rados_connect(*cluster);
|
||||
if (r < 0) {
|
||||
error_setg_errno(errp, -r, "error connecting");
|
||||
goto failed_shutdown;
|
||||
}
|
||||
|
||||
r = rados_ioctx_create(s->cluster, pool, &s->io_ctx);
|
||||
r = rados_ioctx_create(*cluster, opts->pool, io_ctx);
|
||||
if (r < 0) {
|
||||
error_setg_errno(errp, -r, "error opening pool %s", pool);
|
||||
error_setg_errno(errp, -r, "error opening pool %s", opts->pool);
|
||||
goto failed_shutdown;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
failed_shutdown:
|
||||
rados_shutdown(*cluster);
|
||||
failed_opts:
|
||||
g_free(mon_host);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp)
|
||||
{
|
||||
BDRVRBDState *s = bs->opaque;
|
||||
BlockdevOptionsRbd *opts = NULL;
|
||||
Visitor *v;
|
||||
QObject *crumpled = NULL;
|
||||
Error *local_err = NULL;
|
||||
const char *filename;
|
||||
char *keypairs, *secretid;
|
||||
int r;
|
||||
|
||||
/* If we are given a filename, parse the filename, with precedence given to
|
||||
* filename encoded options */
|
||||
filename = qdict_get_try_str(options, "filename");
|
||||
if (filename) {
|
||||
warn_report("'filename' option specified. "
|
||||
"This is an unsupported option, and may be deprecated "
|
||||
"in the future");
|
||||
qemu_rbd_parse_filename(filename, options, &local_err);
|
||||
qdict_del(options, "filename");
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
keypairs = g_strdup(qdict_get_try_str(options, "=keyvalue-pairs"));
|
||||
if (keypairs) {
|
||||
qdict_del(options, "=keyvalue-pairs");
|
||||
}
|
||||
|
||||
secretid = g_strdup(qdict_get_try_str(options, "password-secret"));
|
||||
if (secretid) {
|
||||
qdict_del(options, "password-secret");
|
||||
}
|
||||
|
||||
/* Convert the remaining options into a QAPI object */
|
||||
crumpled = qdict_crumple(options, errp);
|
||||
if (crumpled == NULL) {
|
||||
r = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
v = qobject_input_visitor_new_keyval(crumpled);
|
||||
visit_type_BlockdevOptionsRbd(v, NULL, &opts, &local_err);
|
||||
visit_free(v);
|
||||
qobject_decref(crumpled);
|
||||
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
r = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
r = qemu_rbd_connect(&s->cluster, &s->io_ctx, opts,
|
||||
!(flags & BDRV_O_NOCACHE), keypairs, secretid, errp);
|
||||
if (r < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
s->snap = g_strdup(opts->snapshot);
|
||||
s->image_name = g_strdup(opts->image);
|
||||
|
||||
/* rbd_open is always r/w */
|
||||
r = rbd_open(s->io_ctx, s->image_name, &s->image, s->snap);
|
||||
if (r < 0) {
|
||||
|
@ -683,19 +704,18 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
|
|||
}
|
||||
}
|
||||
|
||||
qemu_opts_del(opts);
|
||||
return 0;
|
||||
r = 0;
|
||||
goto out;
|
||||
|
||||
failed_open:
|
||||
rados_ioctx_destroy(s->io_ctx);
|
||||
failed_shutdown:
|
||||
rados_shutdown(s->cluster);
|
||||
g_free(s->snap);
|
||||
g_free(s->image_name);
|
||||
failed_opts:
|
||||
qemu_opts_del(opts);
|
||||
g_free(mon_host);
|
||||
exit:
|
||||
rados_shutdown(s->cluster);
|
||||
out:
|
||||
qapi_free_BlockdevOptionsRbd(opts);
|
||||
g_free(keypairs);
|
||||
g_free(secretid);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -1093,8 +1113,8 @@ static BlockAIOCB *qemu_rbd_aio_pdiscard(BlockDriverState *bs,
|
|||
#endif
|
||||
|
||||
#ifdef LIBRBD_SUPPORTS_INVALIDATE
|
||||
static void qemu_rbd_invalidate_cache(BlockDriverState *bs,
|
||||
Error **errp)
|
||||
static void coroutine_fn qemu_rbd_co_invalidate_cache(BlockDriverState *bs,
|
||||
Error **errp)
|
||||
{
|
||||
BDRVRBDState *s = bs->opaque;
|
||||
int r = rbd_invalidate_cache(s->image);
|
||||
|
@ -1134,6 +1154,7 @@ static BlockDriver bdrv_rbd = {
|
|||
.bdrv_file_open = qemu_rbd_open,
|
||||
.bdrv_close = qemu_rbd_close,
|
||||
.bdrv_reopen_prepare = qemu_rbd_reopen_prepare,
|
||||
.bdrv_co_create = qemu_rbd_co_create,
|
||||
.bdrv_co_create_opts = qemu_rbd_co_create_opts,
|
||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
||||
.bdrv_get_info = qemu_rbd_getinfo,
|
||||
|
@ -1160,7 +1181,7 @@ static BlockDriver bdrv_rbd = {
|
|||
.bdrv_snapshot_list = qemu_rbd_snap_list,
|
||||
.bdrv_snapshot_goto = qemu_rbd_snap_rollback,
|
||||
#ifdef LIBRBD_SUPPORTS_INVALIDATE
|
||||
.bdrv_invalidate_cache = qemu_rbd_invalidate_cache,
|
||||
.bdrv_co_invalidate_cache = qemu_rbd_co_invalidate_cache,
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
323
block/sheepdog.c
323
block/sheepdog.c
|
@ -15,8 +15,10 @@
|
|||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qapi-visit-sockets.h"
|
||||
#include "qapi/qapi-visit-block-core.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/qobject-input-visitor.h"
|
||||
#include "qapi/qobject-output-visitor.h"
|
||||
#include "qemu/uri.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/option.h"
|
||||
|
@ -533,23 +535,6 @@ static void sd_aio_setup(SheepdogAIOCB *acb, BDRVSheepdogState *s,
|
|||
qemu_co_mutex_unlock(&s->queue_lock);
|
||||
}
|
||||
|
||||
static SocketAddress *sd_socket_address(const char *path,
|
||||
const char *host, const char *port)
|
||||
{
|
||||
SocketAddress *addr = g_new0(SocketAddress, 1);
|
||||
|
||||
if (path) {
|
||||
addr->type = SOCKET_ADDRESS_TYPE_UNIX;
|
||||
addr->u.q_unix.path = g_strdup(path);
|
||||
} else {
|
||||
addr->type = SOCKET_ADDRESS_TYPE_INET;
|
||||
addr->u.inet.host = g_strdup(host ?: SD_DEFAULT_ADDR);
|
||||
addr->u.inet.port = g_strdup(port ?: stringify(SD_DEFAULT_PORT));
|
||||
}
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
static SocketAddress *sd_server_config(QDict *options, Error **errp)
|
||||
{
|
||||
QDict *server = NULL;
|
||||
|
@ -1882,6 +1867,86 @@ out_with_err_set:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int sd_create_prealloc(BlockdevOptionsSheepdog *location, int64_t size,
|
||||
Error **errp)
|
||||
{
|
||||
BlockDriverState *bs;
|
||||
Visitor *v;
|
||||
QObject *obj = NULL;
|
||||
QDict *qdict;
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
|
||||
v = qobject_output_visitor_new(&obj);
|
||||
visit_type_BlockdevOptionsSheepdog(v, NULL, &location, &local_err);
|
||||
visit_free(v);
|
||||
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
qobject_decref(obj);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
qdict = qobject_to_qdict(obj);
|
||||
qdict_flatten(qdict);
|
||||
|
||||
qdict_put_str(qdict, "driver", "sheepdog");
|
||||
|
||||
bs = bdrv_open(NULL, NULL, qdict, BDRV_O_PROTOCOL | BDRV_O_RDWR, errp);
|
||||
if (bs == NULL) {
|
||||
ret = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = sd_prealloc(bs, 0, size, errp);
|
||||
fail:
|
||||
bdrv_unref(bs);
|
||||
QDECREF(qdict);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int parse_redundancy(BDRVSheepdogState *s, SheepdogRedundancy *opt)
|
||||
{
|
||||
struct SheepdogInode *inode = &s->inode;
|
||||
|
||||
switch (opt->type) {
|
||||
case SHEEPDOG_REDUNDANCY_TYPE_FULL:
|
||||
if (opt->u.full.copies > SD_MAX_COPIES || opt->u.full.copies < 1) {
|
||||
return -EINVAL;
|
||||
}
|
||||
inode->copy_policy = 0;
|
||||
inode->nr_copies = opt->u.full.copies;
|
||||
return 0;
|
||||
|
||||
case SHEEPDOG_REDUNDANCY_TYPE_ERASURE_CODED:
|
||||
{
|
||||
int64_t copy = opt->u.erasure_coded.data_strips;
|
||||
int64_t parity = opt->u.erasure_coded.parity_strips;
|
||||
|
||||
if (copy != 2 && copy != 4 && copy != 8 && copy != 16) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (parity >= SD_EC_MAX_STRIP || parity < 1) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* 4 bits for parity and 4 bits for data.
|
||||
* We have to compress upper data bits because it can't represent 16
|
||||
*/
|
||||
inode->copy_policy = ((copy / 2) << 4) + parity;
|
||||
inode->nr_copies = copy + parity;
|
||||
return 0;
|
||||
}
|
||||
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sheepdog support two kinds of redundancy, full replication and erasure
|
||||
* coding.
|
||||
|
@ -1892,60 +1957,61 @@ out_with_err_set:
|
|||
* # create a erasure coded vdi with x data strips and y parity strips
|
||||
* -o redundancy=x:y (x must be one of {2,4,8,16} and 1 <= y < SD_EC_MAX_STRIP)
|
||||
*/
|
||||
static int parse_redundancy(BDRVSheepdogState *s, const char *opt)
|
||||
static SheepdogRedundancy *parse_redundancy_str(const char *opt)
|
||||
{
|
||||
struct SheepdogInode *inode = &s->inode;
|
||||
SheepdogRedundancy *redundancy;
|
||||
const char *n1, *n2;
|
||||
long copy, parity;
|
||||
char p[10];
|
||||
int ret;
|
||||
|
||||
pstrcpy(p, sizeof(p), opt);
|
||||
n1 = strtok(p, ":");
|
||||
n2 = strtok(NULL, ":");
|
||||
|
||||
if (!n1) {
|
||||
return -EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
copy = strtol(n1, NULL, 10);
|
||||
/* FIXME fix error checking by switching to qemu_strtol() */
|
||||
if (copy > SD_MAX_COPIES || copy < 1) {
|
||||
return -EINVAL;
|
||||
ret = qemu_strtol(n1, NULL, 10, ©);
|
||||
if (ret < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
redundancy = g_new0(SheepdogRedundancy, 1);
|
||||
if (!n2) {
|
||||
inode->copy_policy = 0;
|
||||
inode->nr_copies = copy;
|
||||
return 0;
|
||||
*redundancy = (SheepdogRedundancy) {
|
||||
.type = SHEEPDOG_REDUNDANCY_TYPE_FULL,
|
||||
.u.full.copies = copy,
|
||||
};
|
||||
} else {
|
||||
ret = qemu_strtol(n2, NULL, 10, &parity);
|
||||
if (ret < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*redundancy = (SheepdogRedundancy) {
|
||||
.type = SHEEPDOG_REDUNDANCY_TYPE_ERASURE_CODED,
|
||||
.u.erasure_coded = {
|
||||
.data_strips = copy,
|
||||
.parity_strips = parity,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (copy != 2 && copy != 4 && copy != 8 && copy != 16) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
parity = strtol(n2, NULL, 10);
|
||||
/* FIXME fix error checking by switching to qemu_strtol() */
|
||||
if (parity >= SD_EC_MAX_STRIP || parity < 1) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* 4 bits for parity and 4 bits for data.
|
||||
* We have to compress upper data bits because it can't represent 16
|
||||
*/
|
||||
inode->copy_policy = ((copy / 2) << 4) + parity;
|
||||
inode->nr_copies = copy + parity;
|
||||
|
||||
return 0;
|
||||
return redundancy;
|
||||
}
|
||||
|
||||
static int parse_block_size_shift(BDRVSheepdogState *s, QemuOpts *opt)
|
||||
static int parse_block_size_shift(BDRVSheepdogState *s,
|
||||
BlockdevCreateOptionsSheepdog *opts)
|
||||
{
|
||||
struct SheepdogInode *inode = &s->inode;
|
||||
uint64_t object_size;
|
||||
int obj_order;
|
||||
|
||||
object_size = qemu_opt_get_size_del(opt, BLOCK_OPT_OBJECT_SIZE, 0);
|
||||
if (object_size) {
|
||||
if (opts->has_object_size) {
|
||||
object_size = opts->object_size;
|
||||
|
||||
if ((object_size - 1) & object_size) { /* not a power of 2? */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
@ -1959,57 +2025,55 @@ static int parse_block_size_shift(BDRVSheepdogState *s, QemuOpts *opt)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int coroutine_fn sd_co_create_opts(const char *filename, QemuOpts *opts,
|
||||
Error **errp)
|
||||
static int sd_co_create(BlockdevCreateOptions *options, Error **errp)
|
||||
{
|
||||
Error *err = NULL;
|
||||
BlockdevCreateOptionsSheepdog *opts = &options->u.sheepdog;
|
||||
int ret = 0;
|
||||
uint32_t vid = 0;
|
||||
char *backing_file = NULL;
|
||||
char *buf = NULL;
|
||||
BDRVSheepdogState *s;
|
||||
SheepdogConfig cfg;
|
||||
uint64_t max_vdi_size;
|
||||
bool prealloc = false;
|
||||
|
||||
assert(options->driver == BLOCKDEV_DRIVER_SHEEPDOG);
|
||||
|
||||
s = g_new0(BDRVSheepdogState, 1);
|
||||
|
||||
if (strstr(filename, "://")) {
|
||||
sd_parse_uri(&cfg, filename, &err);
|
||||
} else {
|
||||
parse_vdiname(&cfg, filename, &err);
|
||||
}
|
||||
if (err) {
|
||||
error_propagate(errp, err);
|
||||
/* Steal SocketAddress from QAPI, set NULL to prevent double free */
|
||||
s->addr = opts->location->server;
|
||||
opts->location->server = NULL;
|
||||
|
||||
if (strlen(opts->location->vdi) >= sizeof(s->name)) {
|
||||
error_setg(errp, "'vdi' string too long");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
pstrcpy(s->name, sizeof(s->name), opts->location->vdi);
|
||||
|
||||
buf = cfg.port ? g_strdup_printf("%d", cfg.port) : NULL;
|
||||
s->addr = sd_socket_address(cfg.path, cfg.host, buf);
|
||||
g_free(buf);
|
||||
strcpy(s->name, cfg.vdi);
|
||||
sd_config_done(&cfg);
|
||||
s->inode.vdi_size = opts->size;
|
||||
backing_file = opts->backing_file;
|
||||
|
||||
s->inode.vdi_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);
|
||||
buf = qemu_opt_get_del(opts, BLOCK_OPT_PREALLOC);
|
||||
if (!buf || !strcmp(buf, "off")) {
|
||||
if (!opts->has_preallocation) {
|
||||
opts->preallocation = PREALLOC_MODE_OFF;
|
||||
}
|
||||
switch (opts->preallocation) {
|
||||
case PREALLOC_MODE_OFF:
|
||||
prealloc = false;
|
||||
} else if (!strcmp(buf, "full")) {
|
||||
break;
|
||||
case PREALLOC_MODE_FULL:
|
||||
prealloc = true;
|
||||
} else {
|
||||
error_setg(errp, "Invalid preallocation mode: '%s'", buf);
|
||||
break;
|
||||
default:
|
||||
error_setg(errp, "Preallocation mode not supported for Sheepdog");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
g_free(buf);
|
||||
buf = qemu_opt_get_del(opts, BLOCK_OPT_REDUNDANCY);
|
||||
if (buf) {
|
||||
ret = parse_redundancy(s, buf);
|
||||
if (opts->has_redundancy) {
|
||||
ret = parse_redundancy(s, opts->redundancy);
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "Invalid redundancy mode: '%s'", buf);
|
||||
error_setg(errp, "Invalid redundancy mode");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
@ -2021,20 +2085,20 @@ static int coroutine_fn sd_co_create_opts(const char *filename, QemuOpts *opts,
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (backing_file) {
|
||||
if (opts->has_backing_file) {
|
||||
BlockBackend *blk;
|
||||
BDRVSheepdogState *base;
|
||||
BlockDriver *drv;
|
||||
|
||||
/* Currently, only Sheepdog backing image is supported. */
|
||||
drv = bdrv_find_protocol(backing_file, true, NULL);
|
||||
drv = bdrv_find_protocol(opts->backing_file, true, NULL);
|
||||
if (!drv || strcmp(drv->protocol_name, "sheepdog") != 0) {
|
||||
error_setg(errp, "backing_file must be a sheepdog image");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
blk = blk_new_open(backing_file, NULL, NULL,
|
||||
blk = blk_new_open(opts->backing_file, NULL, NULL,
|
||||
BDRV_O_PROTOCOL, errp);
|
||||
if (blk == NULL) {
|
||||
ret = -EIO;
|
||||
|
@ -2102,28 +2166,96 @@ static int coroutine_fn sd_co_create_opts(const char *filename, QemuOpts *opts,
|
|||
}
|
||||
|
||||
if (prealloc) {
|
||||
BlockDriverState *bs;
|
||||
QDict *opts;
|
||||
|
||||
opts = qdict_new();
|
||||
qdict_put_str(opts, "driver", "sheepdog");
|
||||
bs = bdrv_open(filename, NULL, opts, BDRV_O_PROTOCOL | BDRV_O_RDWR,
|
||||
errp);
|
||||
if (!bs) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = sd_prealloc(bs, 0, s->inode.vdi_size, errp);
|
||||
|
||||
bdrv_unref(bs);
|
||||
ret = sd_create_prealloc(opts->location, opts->size, errp);
|
||||
}
|
||||
out:
|
||||
g_free(backing_file);
|
||||
g_free(buf);
|
||||
g_free(s->addr);
|
||||
g_free(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int coroutine_fn sd_co_create_opts(const char *filename, QemuOpts *opts,
|
||||
Error **errp)
|
||||
{
|
||||
BlockdevCreateOptions *create_options = NULL;
|
||||
QDict *qdict, *location_qdict;
|
||||
QObject *crumpled;
|
||||
Visitor *v;
|
||||
const char *redundancy;
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
|
||||
redundancy = qemu_opt_get_del(opts, BLOCK_OPT_REDUNDANCY);
|
||||
|
||||
qdict = qemu_opts_to_qdict(opts, NULL);
|
||||
qdict_put_str(qdict, "driver", "sheepdog");
|
||||
|
||||
location_qdict = qdict_new();
|
||||
qdict_put(qdict, "location", location_qdict);
|
||||
|
||||
sd_parse_filename(filename, location_qdict, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
qdict_flatten(qdict);
|
||||
|
||||
/* Change legacy command line options into QMP ones */
|
||||
static const QDictRenames opt_renames[] = {
|
||||
{ BLOCK_OPT_BACKING_FILE, "backing-file" },
|
||||
{ BLOCK_OPT_OBJECT_SIZE, "object-size" },
|
||||
{ NULL, NULL },
|
||||
};
|
||||
|
||||
if (!qdict_rename_keys(qdict, opt_renames, errp)) {
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Get the QAPI object */
|
||||
crumpled = qdict_crumple(qdict, errp);
|
||||
if (crumpled == NULL) {
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
v = qobject_input_visitor_new_keyval(crumpled);
|
||||
visit_type_BlockdevCreateOptions(v, NULL, &create_options, &local_err);
|
||||
visit_free(v);
|
||||
qobject_decref(crumpled);
|
||||
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
assert(create_options->driver == BLOCKDEV_DRIVER_SHEEPDOG);
|
||||
create_options->u.sheepdog.size =
|
||||
ROUND_UP(create_options->u.sheepdog.size, BDRV_SECTOR_SIZE);
|
||||
|
||||
if (redundancy) {
|
||||
create_options->u.sheepdog.has_redundancy = true;
|
||||
create_options->u.sheepdog.redundancy =
|
||||
parse_redundancy_str(redundancy);
|
||||
if (create_options->u.sheepdog.redundancy == NULL) {
|
||||
error_setg(errp, "Invalid redundancy mode");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
ret = sd_co_create(create_options, errp);
|
||||
fail:
|
||||
qapi_free_BlockdevCreateOptions(create_options);
|
||||
QDECREF(qdict);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sd_close(BlockDriverState *bs)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
|
@ -3103,6 +3235,7 @@ static BlockDriver bdrv_sheepdog = {
|
|||
.bdrv_reopen_commit = sd_reopen_commit,
|
||||
.bdrv_reopen_abort = sd_reopen_abort,
|
||||
.bdrv_close = sd_close,
|
||||
.bdrv_co_create = sd_co_create,
|
||||
.bdrv_co_create_opts = sd_co_create_opts,
|
||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
||||
.bdrv_getlength = sd_getlength,
|
||||
|
@ -3139,6 +3272,7 @@ static BlockDriver bdrv_sheepdog_tcp = {
|
|||
.bdrv_reopen_commit = sd_reopen_commit,
|
||||
.bdrv_reopen_abort = sd_reopen_abort,
|
||||
.bdrv_close = sd_close,
|
||||
.bdrv_co_create = sd_co_create,
|
||||
.bdrv_co_create_opts = sd_co_create_opts,
|
||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
||||
.bdrv_getlength = sd_getlength,
|
||||
|
@ -3175,6 +3309,7 @@ static BlockDriver bdrv_sheepdog_unix = {
|
|||
.bdrv_reopen_commit = sd_reopen_commit,
|
||||
.bdrv_reopen_abort = sd_reopen_abort,
|
||||
.bdrv_close = sd_close,
|
||||
.bdrv_co_create = sd_co_create,
|
||||
.bdrv_co_create_opts = sd_co_create_opts,
|
||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
||||
.bdrv_getlength = sd_getlength,
|
||||
|
|
298
block/ssh.c
298
block/ssh.c
|
@ -35,6 +35,7 @@
|
|||
#include "qemu/sockets.h"
|
||||
#include "qemu/uri.h"
|
||||
#include "qapi/qapi-visit-sockets.h"
|
||||
#include "qapi/qapi-visit-block-core.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "qapi/qobject-input-visitor.h"
|
||||
|
@ -430,31 +431,35 @@ check_host_key_hash(BDRVSSHState *s, const char *hash,
|
|||
}
|
||||
|
||||
static int check_host_key(BDRVSSHState *s, const char *host, int port,
|
||||
const char *host_key_check, Error **errp)
|
||||
SshHostKeyCheck *hkc, Error **errp)
|
||||
{
|
||||
/* host_key_check=no */
|
||||
if (strcmp(host_key_check, "no") == 0) {
|
||||
SshHostKeyCheckMode mode;
|
||||
|
||||
if (hkc) {
|
||||
mode = hkc->mode;
|
||||
} else {
|
||||
mode = SSH_HOST_KEY_CHECK_MODE_KNOWN_HOSTS;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case SSH_HOST_KEY_CHECK_MODE_NONE:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* host_key_check=md5:xx:yy:zz:... */
|
||||
if (strncmp(host_key_check, "md5:", 4) == 0) {
|
||||
return check_host_key_hash(s, &host_key_check[4],
|
||||
LIBSSH2_HOSTKEY_HASH_MD5, 16, errp);
|
||||
}
|
||||
|
||||
/* host_key_check=sha1:xx:yy:zz:... */
|
||||
if (strncmp(host_key_check, "sha1:", 5) == 0) {
|
||||
return check_host_key_hash(s, &host_key_check[5],
|
||||
LIBSSH2_HOSTKEY_HASH_SHA1, 20, errp);
|
||||
}
|
||||
|
||||
/* host_key_check=yes */
|
||||
if (strcmp(host_key_check, "yes") == 0) {
|
||||
case SSH_HOST_KEY_CHECK_MODE_HASH:
|
||||
if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_MD5) {
|
||||
return check_host_key_hash(s, hkc->u.hash.hash,
|
||||
LIBSSH2_HOSTKEY_HASH_MD5, 16, errp);
|
||||
} else if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_SHA1) {
|
||||
return check_host_key_hash(s, hkc->u.hash.hash,
|
||||
LIBSSH2_HOSTKEY_HASH_SHA1, 20, errp);
|
||||
}
|
||||
g_assert_not_reached();
|
||||
break;
|
||||
case SSH_HOST_KEY_CHECK_MODE_KNOWN_HOSTS:
|
||||
return check_host_key_knownhosts(s, host, port, errp);
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
|
||||
error_setg(errp, "unknown host_key_check setting (%s)", host_key_check);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
@ -543,16 +548,6 @@ static QemuOptsList ssh_runtime_opts = {
|
|||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "Port to connect to",
|
||||
},
|
||||
{
|
||||
.name = "path",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Path of the image on the host",
|
||||
},
|
||||
{
|
||||
.name = "user",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "User as which to connect",
|
||||
},
|
||||
{
|
||||
.name = "host_key_check",
|
||||
.type = QEMU_OPT_STRING,
|
||||
|
@ -562,12 +557,13 @@ static QemuOptsList ssh_runtime_opts = {
|
|||
},
|
||||
};
|
||||
|
||||
static bool ssh_process_legacy_socket_options(QDict *output_opts,
|
||||
QemuOpts *legacy_opts,
|
||||
Error **errp)
|
||||
static bool ssh_process_legacy_options(QDict *output_opts,
|
||||
QemuOpts *legacy_opts,
|
||||
Error **errp)
|
||||
{
|
||||
const char *host = qemu_opt_get(legacy_opts, "host");
|
||||
const char *port = qemu_opt_get(legacy_opts, "port");
|
||||
const char *host_key_check = qemu_opt_get(legacy_opts, "host_key_check");
|
||||
|
||||
if (!host && port) {
|
||||
error_setg(errp, "port may not be used without host");
|
||||
|
@ -579,26 +575,56 @@ static bool ssh_process_legacy_socket_options(QDict *output_opts,
|
|||
qdict_put_str(output_opts, "server.port", port ?: stringify(22));
|
||||
}
|
||||
|
||||
if (host_key_check) {
|
||||
if (strcmp(host_key_check, "no") == 0) {
|
||||
qdict_put_str(output_opts, "host-key-check.mode", "none");
|
||||
} else if (strncmp(host_key_check, "md5:", 4) == 0) {
|
||||
qdict_put_str(output_opts, "host-key-check.mode", "hash");
|
||||
qdict_put_str(output_opts, "host-key-check.type", "md5");
|
||||
qdict_put_str(output_opts, "host-key-check.hash",
|
||||
&host_key_check[4]);
|
||||
} else if (strncmp(host_key_check, "sha1:", 5) == 0) {
|
||||
qdict_put_str(output_opts, "host-key-check.mode", "hash");
|
||||
qdict_put_str(output_opts, "host-key-check.type", "sha1");
|
||||
qdict_put_str(output_opts, "host-key-check.hash",
|
||||
&host_key_check[5]);
|
||||
} else if (strcmp(host_key_check, "yes") == 0) {
|
||||
qdict_put_str(output_opts, "host-key-check.mode", "known_hosts");
|
||||
} else {
|
||||
error_setg(errp, "unknown host_key_check setting (%s)",
|
||||
host_key_check);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static InetSocketAddress *ssh_config(QDict *options, Error **errp)
|
||||
static BlockdevOptionsSsh *ssh_parse_options(QDict *options, Error **errp)
|
||||
{
|
||||
InetSocketAddress *inet = NULL;
|
||||
QDict *addr = NULL;
|
||||
QObject *crumpled_addr = NULL;
|
||||
Visitor *iv = NULL;
|
||||
Error *local_error = NULL;
|
||||
BlockdevOptionsSsh *result = NULL;
|
||||
QemuOpts *opts = NULL;
|
||||
Error *local_err = NULL;
|
||||
QObject *crumpled;
|
||||
const QDictEntry *e;
|
||||
Visitor *v;
|
||||
|
||||
qdict_extract_subqdict(options, &addr, "server.");
|
||||
if (!qdict_size(addr)) {
|
||||
error_setg(errp, "SSH server address missing");
|
||||
goto out;
|
||||
/* Translate legacy options */
|
||||
opts = qemu_opts_create(&ssh_runtime_opts, NULL, 0, &error_abort);
|
||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
crumpled_addr = qdict_crumple(addr, errp);
|
||||
if (!crumpled_addr) {
|
||||
goto out;
|
||||
if (!ssh_process_legacy_options(options, opts, errp)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Create the QAPI object */
|
||||
crumpled = qdict_crumple(options, errp);
|
||||
if (crumpled == NULL) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -609,51 +635,37 @@ static InetSocketAddress *ssh_config(QDict *options, Error **errp)
|
|||
* but when they come from -drive, they're all QString. The
|
||||
* visitor expects the former.
|
||||
*/
|
||||
iv = qobject_input_visitor_new(crumpled_addr);
|
||||
visit_type_InetSocketAddress(iv, NULL, &inet, &local_error);
|
||||
if (local_error) {
|
||||
error_propagate(errp, local_error);
|
||||
goto out;
|
||||
v = qobject_input_visitor_new(crumpled);
|
||||
visit_type_BlockdevOptionsSsh(v, NULL, &result, &local_err);
|
||||
visit_free(v);
|
||||
qobject_decref(crumpled);
|
||||
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
out:
|
||||
QDECREF(addr);
|
||||
qobject_decref(crumpled_addr);
|
||||
visit_free(iv);
|
||||
return inet;
|
||||
/* Remove the processed options from the QDict (the visitor processes
|
||||
* _all_ options in the QDict) */
|
||||
while ((e = qdict_first(options))) {
|
||||
qdict_del(options, e->key);
|
||||
}
|
||||
|
||||
fail:
|
||||
qemu_opts_del(opts);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int connect_to_ssh(BDRVSSHState *s, QDict *options,
|
||||
static int connect_to_ssh(BDRVSSHState *s, BlockdevOptionsSsh *opts,
|
||||
int ssh_flags, int creat_mode, Error **errp)
|
||||
{
|
||||
int r, ret;
|
||||
QemuOpts *opts = NULL;
|
||||
Error *local_err = NULL;
|
||||
const char *user, *path, *host_key_check;
|
||||
const char *user;
|
||||
long port = 0;
|
||||
|
||||
opts = qemu_opts_create(&ssh_runtime_opts, NULL, 0, &error_abort);
|
||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||
if (local_err) {
|
||||
ret = -EINVAL;
|
||||
error_propagate(errp, local_err);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!ssh_process_legacy_socket_options(options, opts, errp)) {
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
path = qemu_opt_get(opts, "path");
|
||||
if (!path) {
|
||||
ret = -EINVAL;
|
||||
error_setg(errp, "No path was specified");
|
||||
goto err;
|
||||
}
|
||||
|
||||
user = qemu_opt_get(opts, "user");
|
||||
if (!user) {
|
||||
if (opts->has_user) {
|
||||
user = opts->user;
|
||||
} else {
|
||||
user = g_get_user_name();
|
||||
if (!user) {
|
||||
error_setg_errno(errp, errno, "Can't get user name");
|
||||
|
@ -662,17 +674,9 @@ static int connect_to_ssh(BDRVSSHState *s, QDict *options,
|
|||
}
|
||||
}
|
||||
|
||||
host_key_check = qemu_opt_get(opts, "host_key_check");
|
||||
if (!host_key_check) {
|
||||
host_key_check = "yes";
|
||||
}
|
||||
|
||||
/* Pop the config into our state object, Exit if invalid */
|
||||
s->inet = ssh_config(options, errp);
|
||||
if (!s->inet) {
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
s->inet = opts->server;
|
||||
opts->server = NULL;
|
||||
|
||||
if (qemu_strtol(s->inet->port, NULL, 10, &port) < 0) {
|
||||
error_setg(errp, "Use only numeric port value");
|
||||
|
@ -707,8 +711,7 @@ static int connect_to_ssh(BDRVSSHState *s, QDict *options,
|
|||
}
|
||||
|
||||
/* Check the remote host's key against known_hosts. */
|
||||
ret = check_host_key(s, s->inet->host, port, host_key_check,
|
||||
errp);
|
||||
ret = check_host_key(s, s->inet->host, port, opts->host_key_check, errp);
|
||||
if (ret < 0) {
|
||||
goto err;
|
||||
}
|
||||
|
@ -729,16 +732,16 @@ static int connect_to_ssh(BDRVSSHState *s, QDict *options,
|
|||
|
||||
/* Open the remote file. */
|
||||
DPRINTF("opening file %s flags=0x%x creat_mode=0%o",
|
||||
path, ssh_flags, creat_mode);
|
||||
s->sftp_handle = libssh2_sftp_open(s->sftp, path, ssh_flags, creat_mode);
|
||||
opts->path, ssh_flags, creat_mode);
|
||||
s->sftp_handle = libssh2_sftp_open(s->sftp, opts->path, ssh_flags,
|
||||
creat_mode);
|
||||
if (!s->sftp_handle) {
|
||||
session_error_setg(errp, s, "failed to open remote file '%s'", path);
|
||||
session_error_setg(errp, s, "failed to open remote file '%s'",
|
||||
opts->path);
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
qemu_opts_del(opts);
|
||||
|
||||
r = libssh2_sftp_fstat(s->sftp_handle, &s->attrs);
|
||||
if (r < 0) {
|
||||
sftp_error_setg(errp, s, "failed to read file attributes");
|
||||
|
@ -764,8 +767,6 @@ static int connect_to_ssh(BDRVSSHState *s, QDict *options,
|
|||
}
|
||||
s->session = NULL;
|
||||
|
||||
qemu_opts_del(opts);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -773,6 +774,7 @@ static int ssh_file_open(BlockDriverState *bs, QDict *options, int bdrv_flags,
|
|||
Error **errp)
|
||||
{
|
||||
BDRVSSHState *s = bs->opaque;
|
||||
BlockdevOptionsSsh *opts;
|
||||
int ret;
|
||||
int ssh_flags;
|
||||
|
||||
|
@ -783,8 +785,13 @@ static int ssh_file_open(BlockDriverState *bs, QDict *options, int bdrv_flags,
|
|||
ssh_flags |= LIBSSH2_FXF_WRITE;
|
||||
}
|
||||
|
||||
opts = ssh_parse_options(options, errp);
|
||||
if (opts == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Start up SSH. */
|
||||
ret = connect_to_ssh(s, options, ssh_flags, 0, errp);
|
||||
ret = connect_to_ssh(s, opts, ssh_flags, 0, errp);
|
||||
if (ret < 0) {
|
||||
goto err;
|
||||
}
|
||||
|
@ -792,6 +799,8 @@ static int ssh_file_open(BlockDriverState *bs, QDict *options, int bdrv_flags,
|
|||
/* Go non-blocking. */
|
||||
libssh2_session_set_blocking(s->session, 0);
|
||||
|
||||
qapi_free_BlockdevOptionsSsh(opts);
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
|
@ -800,6 +809,8 @@ static int ssh_file_open(BlockDriverState *bs, QDict *options, int bdrv_flags,
|
|||
}
|
||||
s->sock = -1;
|
||||
|
||||
qapi_free_BlockdevOptionsSsh(opts);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -843,51 +854,71 @@ static QemuOptsList ssh_create_opts = {
|
|||
}
|
||||
};
|
||||
|
||||
static int coroutine_fn ssh_co_create_opts(const char *filename, QemuOpts *opts,
|
||||
Error **errp)
|
||||
static int ssh_co_create(BlockdevCreateOptions *options, Error **errp)
|
||||
{
|
||||
int r, ret;
|
||||
int64_t total_size = 0;
|
||||
QDict *uri_options = NULL;
|
||||
BlockdevCreateOptionsSsh *opts = &options->u.ssh;
|
||||
BDRVSSHState s;
|
||||
int ret;
|
||||
|
||||
assert(options->driver == BLOCKDEV_DRIVER_SSH);
|
||||
|
||||
ssh_state_init(&s);
|
||||
|
||||
/* Get desired file size. */
|
||||
total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
||||
BDRV_SECTOR_SIZE);
|
||||
DPRINTF("total_size=%" PRIi64, total_size);
|
||||
|
||||
uri_options = qdict_new();
|
||||
r = parse_uri(filename, uri_options, errp);
|
||||
if (r < 0) {
|
||||
ret = r;
|
||||
goto out;
|
||||
ret = connect_to_ssh(&s, opts->location,
|
||||
LIBSSH2_FXF_READ|LIBSSH2_FXF_WRITE|
|
||||
LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC,
|
||||
0644, errp);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
r = connect_to_ssh(&s, uri_options,
|
||||
LIBSSH2_FXF_READ|LIBSSH2_FXF_WRITE|
|
||||
LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC,
|
||||
0644, errp);
|
||||
if (r < 0) {
|
||||
ret = r;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (total_size > 0) {
|
||||
ret = ssh_grow_file(&s, total_size, errp);
|
||||
if (opts->size > 0) {
|
||||
ret = ssh_grow_file(&s, opts->size, errp);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
fail:
|
||||
ssh_state_free(&s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int coroutine_fn ssh_co_create_opts(const char *filename, QemuOpts *opts,
|
||||
Error **errp)
|
||||
{
|
||||
BlockdevCreateOptions *create_options;
|
||||
BlockdevCreateOptionsSsh *ssh_opts;
|
||||
int ret;
|
||||
QDict *uri_options = NULL;
|
||||
|
||||
create_options = g_new0(BlockdevCreateOptions, 1);
|
||||
create_options->driver = BLOCKDEV_DRIVER_SSH;
|
||||
ssh_opts = &create_options->u.ssh;
|
||||
|
||||
/* Get desired file size. */
|
||||
ssh_opts->size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
||||
BDRV_SECTOR_SIZE);
|
||||
DPRINTF("total_size=%" PRIi64, ssh_opts->size);
|
||||
|
||||
uri_options = qdict_new();
|
||||
ret = parse_uri(filename, uri_options, errp);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ssh_opts->location = ssh_parse_options(uri_options, errp);
|
||||
if (ssh_opts->location == NULL) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = ssh_co_create(create_options, errp);
|
||||
|
||||
out:
|
||||
ssh_state_free(&s);
|
||||
if (uri_options != NULL) {
|
||||
QDECREF(uri_options);
|
||||
}
|
||||
QDECREF(uri_options);
|
||||
qapi_free_BlockdevCreateOptions(create_options);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1249,6 +1280,7 @@ static BlockDriver bdrv_ssh = {
|
|||
.instance_size = sizeof(BDRVSSHState),
|
||||
.bdrv_parse_filename = ssh_parse_filename,
|
||||
.bdrv_file_open = ssh_file_open,
|
||||
.bdrv_co_create = ssh_co_create,
|
||||
.bdrv_co_create_opts = ssh_co_create_opts,
|
||||
.bdrv_close = ssh_close,
|
||||
.bdrv_has_zero_init = ssh_has_zero_init,
|
||||
|
|
|
@ -263,8 +263,8 @@ static void vdi_header_print(VdiHeader *header)
|
|||
}
|
||||
#endif
|
||||
|
||||
static int vdi_check(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
BdrvCheckMode fix)
|
||||
static int coroutine_fn vdi_co_check(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
BdrvCheckMode fix)
|
||||
{
|
||||
/* TODO: additional checks possible. */
|
||||
BDRVVdiState *s = (BDRVVdiState *)bs->opaque;
|
||||
|
@ -908,7 +908,7 @@ static BlockDriver bdrv_vdi = {
|
|||
.bdrv_get_info = vdi_get_info,
|
||||
|
||||
.create_opts = &vdi_create_opts,
|
||||
.bdrv_check = vdi_check,
|
||||
.bdrv_co_check = vdi_co_check,
|
||||
};
|
||||
|
||||
static void bdrv_vdi_init(void)
|
||||
|
|
|
@ -1944,8 +1944,9 @@ exit:
|
|||
* r/w and any log has already been replayed, so there is nothing (currently)
|
||||
* for us to do here
|
||||
*/
|
||||
static int vhdx_check(BlockDriverState *bs, BdrvCheckResult *result,
|
||||
BdrvCheckMode fix)
|
||||
static int coroutine_fn vhdx_co_check(BlockDriverState *bs,
|
||||
BdrvCheckResult *result,
|
||||
BdrvCheckMode fix)
|
||||
{
|
||||
BDRVVHDXState *s = bs->opaque;
|
||||
|
||||
|
@ -2006,7 +2007,7 @@ static BlockDriver bdrv_vhdx = {
|
|||
.bdrv_co_writev = vhdx_co_writev,
|
||||
.bdrv_co_create_opts = vhdx_co_create_opts,
|
||||
.bdrv_get_info = vhdx_get_info,
|
||||
.bdrv_check = vhdx_check,
|
||||
.bdrv_co_check = vhdx_co_check,
|
||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
||||
|
||||
.create_opts = &vhdx_create_opts,
|
||||
|
|
|
@ -2221,8 +2221,9 @@ static ImageInfo *vmdk_get_extent_info(VmdkExtent *extent)
|
|||
return info;
|
||||
}
|
||||
|
||||
static int vmdk_check(BlockDriverState *bs, BdrvCheckResult *result,
|
||||
BdrvCheckMode fix)
|
||||
static int coroutine_fn vmdk_co_check(BlockDriverState *bs,
|
||||
BdrvCheckResult *result,
|
||||
BdrvCheckMode fix)
|
||||
{
|
||||
BDRVVmdkState *s = bs->opaque;
|
||||
VmdkExtent *extent = NULL;
|
||||
|
@ -2391,7 +2392,7 @@ static BlockDriver bdrv_vmdk = {
|
|||
.instance_size = sizeof(BDRVVmdkState),
|
||||
.bdrv_probe = vmdk_probe,
|
||||
.bdrv_open = vmdk_open,
|
||||
.bdrv_check = vmdk_check,
|
||||
.bdrv_co_check = vmdk_co_check,
|
||||
.bdrv_reopen_prepare = vmdk_reopen_prepare,
|
||||
.bdrv_child_perm = bdrv_format_default_perms,
|
||||
.bdrv_co_preadv = vmdk_co_preadv,
|
||||
|
|
|
@ -226,6 +226,7 @@ char *bdrv_perm_names(uint64_t perm);
|
|||
void bdrv_init(void);
|
||||
void bdrv_init_with_whitelist(void);
|
||||
bool bdrv_uses_whitelist(void);
|
||||
int bdrv_is_whitelisted(BlockDriver *drv, bool read_only);
|
||||
BlockDriver *bdrv_find_protocol(const char *filename,
|
||||
bool allow_protocol_prefix,
|
||||
Error **errp);
|
||||
|
@ -246,6 +247,7 @@ BdrvChild *bdrv_open_child(const char *filename,
|
|||
BlockDriverState* parent,
|
||||
const BdrvChildRole *child_role,
|
||||
bool allow_none, Error **errp);
|
||||
BlockDriverState *bdrv_open_blockdev_ref(BlockdevRef *ref, Error **errp);
|
||||
void bdrv_set_backing_hd(BlockDriverState *bs, BlockDriverState *backing_hd,
|
||||
Error **errp);
|
||||
int bdrv_open_backing_file(BlockDriverState *bs, QDict *parent_options,
|
||||
|
|
|
@ -129,8 +129,11 @@ struct BlockDriver {
|
|||
int (*bdrv_file_open)(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp);
|
||||
void (*bdrv_close)(BlockDriverState *bs);
|
||||
int coroutine_fn (*bdrv_co_create_opts)(const char *filename, QemuOpts *opts,
|
||||
int coroutine_fn (*bdrv_co_create)(BlockdevCreateOptions *opts,
|
||||
Error **errp);
|
||||
int coroutine_fn (*bdrv_co_create_opts)(const char *filename,
|
||||
QemuOpts *opts,
|
||||
Error **errp);
|
||||
int (*bdrv_make_empty)(BlockDriverState *bs);
|
||||
|
||||
void (*bdrv_refresh_filename)(BlockDriverState *bs, QDict *options);
|
||||
|
@ -224,7 +227,8 @@ struct BlockDriver {
|
|||
/*
|
||||
* Invalidate any cached meta-data.
|
||||
*/
|
||||
void (*bdrv_invalidate_cache)(BlockDriverState *bs, Error **errp);
|
||||
void coroutine_fn (*bdrv_co_invalidate_cache)(BlockDriverState *bs,
|
||||
Error **errp);
|
||||
int (*bdrv_inactivate)(BlockDriverState *bs);
|
||||
|
||||
/*
|
||||
|
@ -306,8 +310,9 @@ struct BlockDriver {
|
|||
* Returns 0 for completed check, -errno for internal errors.
|
||||
* The check results are stored in result.
|
||||
*/
|
||||
int (*bdrv_check)(BlockDriverState *bs, BdrvCheckResult *result,
|
||||
BdrvCheckMode fix);
|
||||
int coroutine_fn (*bdrv_co_check)(BlockDriverState *bs,
|
||||
BdrvCheckResult *result,
|
||||
BdrvCheckMode fix);
|
||||
|
||||
int (*bdrv_amend_options)(BlockDriverState *bs, QemuOpts *opts,
|
||||
BlockDriverAmendStatusCB *status_cb,
|
||||
|
|
|
@ -81,4 +81,10 @@ QObject *qdict_crumple(const QDict *src, Error **errp);
|
|||
|
||||
void qdict_join(QDict *dest, QDict *src, bool overwrite);
|
||||
|
||||
typedef struct QDictRenames {
|
||||
const char *from;
|
||||
const char *to;
|
||||
} QDictRenames;
|
||||
bool qdict_rename_keys(QDict *qdict, const QDictRenames *renames, Error **errp);
|
||||
|
||||
#endif /* QDICT_H */
|
||||
|
|
|
@ -124,6 +124,8 @@ void qemu_opts_set_defaults(QemuOptsList *list, const char *params,
|
|||
int permit_abbrev);
|
||||
QemuOpts *qemu_opts_from_qdict(QemuOptsList *list, const QDict *qdict,
|
||||
Error **errp);
|
||||
QDict *qemu_opts_to_qdict_filtered(QemuOpts *opts, QDict *qdict,
|
||||
QemuOptsList *list, bool del);
|
||||
QDict *qemu_opts_to_qdict(QemuOpts *opts, QDict *qdict);
|
||||
void qemu_opts_absorb_qdict(QemuOpts *opts, QDict *qdict, Error **errp);
|
||||
|
||||
|
|
|
@ -2552,6 +2552,63 @@
|
|||
'*cache-clean-interval': 'int',
|
||||
'*encrypt': 'BlockdevQcow2Encryption' } }
|
||||
|
||||
##
|
||||
# @SshHostKeyCheckMode:
|
||||
#
|
||||
# @none Don't check the host key at all
|
||||
# @hash Compare the host key with a given hash
|
||||
# @known_hosts Check the host key against the known_hosts file
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'enum': 'SshHostKeyCheckMode',
|
||||
'data': [ 'none', 'hash', 'known_hosts' ] }
|
||||
|
||||
##
|
||||
# @SshHostKeyCheckHashType:
|
||||
#
|
||||
# @md5 The given hash is an md5 hash
|
||||
# @sha1 The given hash is an sha1 hash
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'enum': 'SshHostKeyCheckHashType',
|
||||
'data': [ 'md5', 'sha1' ] }
|
||||
|
||||
##
|
||||
# @SshHostKeyHash:
|
||||
#
|
||||
# @type The hash algorithm used for the hash
|
||||
# @hash The expected hash value
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'struct': 'SshHostKeyHash',
|
||||
'data': { 'type': 'SshHostKeyCheckHashType',
|
||||
'hash': 'str' }}
|
||||
|
||||
##
|
||||
# @SshHostKeyDummy:
|
||||
#
|
||||
# For those union branches that don't need additional fields.
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'struct': 'SshHostKeyDummy',
|
||||
'data': {} }
|
||||
|
||||
##
|
||||
# @SshHostKeyCheck:
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'union': 'SshHostKeyCheck',
|
||||
'base': { 'mode': 'SshHostKeyCheckMode' },
|
||||
'discriminator': 'mode',
|
||||
'data': { 'none': 'SshHostKeyDummy',
|
||||
'hash': 'SshHostKeyHash',
|
||||
'known_hosts': 'SshHostKeyDummy' } }
|
||||
|
||||
##
|
||||
# @BlockdevOptionsSsh:
|
||||
#
|
||||
|
@ -2562,14 +2619,16 @@
|
|||
# @user: user as which to connect, defaults to current
|
||||
# local user name
|
||||
#
|
||||
# TODO: Expose the host_key_check option in QMP
|
||||
# @host-key-check: Defines how and what to check the host key against
|
||||
# (default: known_hosts)
|
||||
#
|
||||
# Since: 2.9
|
||||
##
|
||||
{ 'struct': 'BlockdevOptionsSsh',
|
||||
'data': { 'server': 'InetSocketAddress',
|
||||
'path': 'str',
|
||||
'*user': 'str' } }
|
||||
'*user': 'str',
|
||||
'*host-key-check': 'SshHostKeyCheck' } }
|
||||
|
||||
|
||||
##
|
||||
|
@ -3358,6 +3417,269 @@
|
|||
##
|
||||
{ 'command': 'blockdev-del', 'data': { 'node-name': 'str' } }
|
||||
|
||||
##
|
||||
# @BlockdevCreateOptionsFile:
|
||||
#
|
||||
# Driver specific image creation options for file.
|
||||
#
|
||||
# @filename Filename for the new image file
|
||||
# @size Size of the virtual disk in bytes
|
||||
# @preallocation Preallocation mode for the new image (default: off)
|
||||
# @nocow Turn off copy-on-write (valid only on btrfs; default: off)
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'struct': 'BlockdevCreateOptionsFile',
|
||||
'data': { 'filename': 'str',
|
||||
'size': 'size',
|
||||
'*preallocation': 'PreallocMode',
|
||||
'*nocow': 'bool' } }
|
||||
|
||||
##
|
||||
# @BlockdevCreateOptionsGluster:
|
||||
#
|
||||
# Driver specific image creation options for gluster.
|
||||
#
|
||||
# @location Where to store the new image file
|
||||
# @size Size of the virtual disk in bytes
|
||||
# @preallocation Preallocation mode for the new image (default: off)
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'struct': 'BlockdevCreateOptionsGluster',
|
||||
'data': { 'location': 'BlockdevOptionsGluster',
|
||||
'size': 'size',
|
||||
'*preallocation': 'PreallocMode' } }
|
||||
|
||||
##
|
||||
# @BlockdevCreateOptionsNfs:
|
||||
#
|
||||
# Driver specific image creation options for NFS.
|
||||
#
|
||||
# @location Where to store the new image file
|
||||
# @size Size of the virtual disk in bytes
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'struct': 'BlockdevCreateOptionsNfs',
|
||||
'data': { 'location': 'BlockdevOptionsNfs',
|
||||
'size': 'size' } }
|
||||
|
||||
##
|
||||
# @BlockdevQcow2Version:
|
||||
#
|
||||
# @v2: The original QCOW2 format as introduced in qemu 0.10 (version 2)
|
||||
# @v3: The extended QCOW2 format as introduced in qemu 1.1 (version 3)
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'enum': 'BlockdevQcow2Version',
|
||||
'data': [ 'v2', 'v3' ] }
|
||||
|
||||
|
||||
##
|
||||
# @BlockdevCreateOptionsQcow2:
|
||||
#
|
||||
# Driver specific image creation options for qcow2.
|
||||
#
|
||||
# @file Node to create the image format on
|
||||
# @size Size of the virtual disk in bytes
|
||||
# @version Compatibility level (default: v3)
|
||||
# @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
|
||||
# @encrypt Encryption options if the image should be encrypted
|
||||
# @cluster-size qcow2 cluster size in bytes (default: 65536)
|
||||
# @preallocation Preallocation mode for the new image (default: off)
|
||||
# @lazy-refcounts True if refcounts may be updated lazily (default: off)
|
||||
# @refcount-bits Width of reference counts in bits (default: 16)
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'struct': 'BlockdevCreateOptionsQcow2',
|
||||
'data': { 'file': 'BlockdevRef',
|
||||
'size': 'size',
|
||||
'*version': 'BlockdevQcow2Version',
|
||||
'*backing-file': 'str',
|
||||
'*backing-fmt': 'BlockdevDriver',
|
||||
'*encrypt': 'QCryptoBlockCreateOptions',
|
||||
'*cluster-size': 'size',
|
||||
'*preallocation': 'PreallocMode',
|
||||
'*lazy-refcounts': 'bool',
|
||||
'*refcount-bits': 'int' } }
|
||||
|
||||
##
|
||||
# @BlockdevCreateOptionsRbd:
|
||||
#
|
||||
# Driver specific image creation options for rbd/Ceph.
|
||||
#
|
||||
# @location Where to store the new image file. This location cannot
|
||||
# point to a snapshot.
|
||||
# @size Size of the virtual disk in bytes
|
||||
# @cluster-size RBD object size
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'struct': 'BlockdevCreateOptionsRbd',
|
||||
'data': { 'location': 'BlockdevOptionsRbd',
|
||||
'size': 'size',
|
||||
'*cluster-size' : 'size' } }
|
||||
|
||||
##
|
||||
# @SheepdogRedundancyType:
|
||||
#
|
||||
# @full Create a fully replicated vdi with x copies
|
||||
# @erasure-coded Create an erasure coded vdi with x data strips and
|
||||
# y parity strips
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'enum': 'SheepdogRedundancyType',
|
||||
'data': [ 'full', 'erasure-coded' ] }
|
||||
|
||||
##
|
||||
# @SheepdogRedundancyFull:
|
||||
#
|
||||
# @copies Number of copies to use (between 1 and 31)
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'struct': 'SheepdogRedundancyFull',
|
||||
'data': { 'copies': 'int' }}
|
||||
|
||||
##
|
||||
# @SheepdogRedundancyErasureCoded:
|
||||
#
|
||||
# @data-strips Number of data strips to use (one of {2,4,8,16})
|
||||
# @parity-strips Number of parity strips to use (between 1 and 15)
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'struct': 'SheepdogRedundancyErasureCoded',
|
||||
'data': { 'data-strips': 'int',
|
||||
'parity-strips': 'int' }}
|
||||
|
||||
##
|
||||
# @SheepdogRedundancy:
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'union': 'SheepdogRedundancy',
|
||||
'base': { 'type': 'SheepdogRedundancyType' },
|
||||
'discriminator': 'type',
|
||||
'data': { 'full': 'SheepdogRedundancyFull',
|
||||
'erasure-coded': 'SheepdogRedundancyErasureCoded' } }
|
||||
|
||||
##
|
||||
# @BlockdevCreateOptionsSheepdog:
|
||||
#
|
||||
# Driver specific image creation options for Sheepdog.
|
||||
#
|
||||
# @location Where to store the new image file
|
||||
# @size Size of the virtual disk in bytes
|
||||
# @backing-file File name of a base image
|
||||
# @preallocation Preallocation mode (allowed values: off, full)
|
||||
# @redundancy Redundancy of the image
|
||||
# @object-size Object size of the image
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'struct': 'BlockdevCreateOptionsSheepdog',
|
||||
'data': { 'location': 'BlockdevOptionsSheepdog',
|
||||
'size': 'size',
|
||||
'*backing-file': 'str',
|
||||
'*preallocation': 'PreallocMode',
|
||||
'*redundancy': 'SheepdogRedundancy',
|
||||
'*object-size': 'size' } }
|
||||
|
||||
##
|
||||
# @BlockdevCreateOptionsSsh:
|
||||
#
|
||||
# Driver specific image creation options for SSH.
|
||||
#
|
||||
# @location Where to store the new image file
|
||||
# @size Size of the virtual disk in bytes
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'struct': 'BlockdevCreateOptionsSsh',
|
||||
'data': { 'location': 'BlockdevOptionsSsh',
|
||||
'size': 'size' } }
|
||||
|
||||
##
|
||||
# @BlockdevCreateNotSupported:
|
||||
#
|
||||
# This is used for all drivers that don't support creating images.
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'struct': 'BlockdevCreateNotSupported', 'data': {}}
|
||||
|
||||
##
|
||||
# @BlockdevCreateOptions:
|
||||
#
|
||||
# Options for creating an image format on a given node.
|
||||
#
|
||||
# @driver block driver to create the image format
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'union': 'BlockdevCreateOptions',
|
||||
'base': {
|
||||
'driver': 'BlockdevDriver' },
|
||||
'discriminator': 'driver',
|
||||
'data': {
|
||||
'blkdebug': 'BlockdevCreateNotSupported',
|
||||
'blkverify': 'BlockdevCreateNotSupported',
|
||||
'bochs': 'BlockdevCreateNotSupported',
|
||||
'cloop': 'BlockdevCreateNotSupported',
|
||||
'dmg': 'BlockdevCreateNotSupported',
|
||||
'file': 'BlockdevCreateOptionsFile',
|
||||
'ftp': 'BlockdevCreateNotSupported',
|
||||
'ftps': 'BlockdevCreateNotSupported',
|
||||
'gluster': 'BlockdevCreateOptionsGluster',
|
||||
'host_cdrom': 'BlockdevCreateNotSupported',
|
||||
'host_device': 'BlockdevCreateNotSupported',
|
||||
'http': 'BlockdevCreateNotSupported',
|
||||
'https': 'BlockdevCreateNotSupported',
|
||||
'iscsi': 'BlockdevCreateNotSupported',
|
||||
'luks': 'BlockdevCreateNotSupported',
|
||||
'nbd': 'BlockdevCreateNotSupported',
|
||||
'nfs': 'BlockdevCreateOptionsNfs',
|
||||
'null-aio': 'BlockdevCreateNotSupported',
|
||||
'null-co': 'BlockdevCreateNotSupported',
|
||||
'nvme': 'BlockdevCreateNotSupported',
|
||||
'parallels': 'BlockdevCreateNotSupported',
|
||||
'qcow2': 'BlockdevCreateOptionsQcow2',
|
||||
'qcow': 'BlockdevCreateNotSupported',
|
||||
'qed': 'BlockdevCreateNotSupported',
|
||||
'quorum': 'BlockdevCreateNotSupported',
|
||||
'raw': 'BlockdevCreateNotSupported',
|
||||
'rbd': 'BlockdevCreateOptionsRbd',
|
||||
'replication': 'BlockdevCreateNotSupported',
|
||||
'sheepdog': 'BlockdevCreateOptionsSheepdog',
|
||||
'ssh': 'BlockdevCreateOptionsSsh',
|
||||
'throttle': 'BlockdevCreateNotSupported',
|
||||
'vdi': 'BlockdevCreateNotSupported',
|
||||
'vhdx': 'BlockdevCreateNotSupported',
|
||||
'vmdk': 'BlockdevCreateNotSupported',
|
||||
'vpc': 'BlockdevCreateNotSupported',
|
||||
'vvfat': 'BlockdevCreateNotSupported',
|
||||
'vxhs': 'BlockdevCreateNotSupported'
|
||||
} }
|
||||
|
||||
##
|
||||
# @x-blockdev-create:
|
||||
#
|
||||
# Create an image format on a given node.
|
||||
# TODO Replace with something asynchronous (block job?)
|
||||
#
|
||||
# Since: 2.12
|
||||
##
|
||||
{ 'command': 'x-blockdev-create',
|
||||
'data': 'BlockdevCreateOptions',
|
||||
'boxed': true }
|
||||
|
||||
##
|
||||
# @blockdev-open-tray:
|
||||
#
|
||||
|
|
|
@ -1072,3 +1072,37 @@ void qdict_join(QDict *dest, QDict *src, bool overwrite)
|
|||
entry = next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* qdict_rename_keys(): Rename keys in qdict according to the replacements
|
||||
* specified in the array renames. The array must be terminated by an entry
|
||||
* with from = NULL.
|
||||
*
|
||||
* The renames are performed individually in the order of the array, so entries
|
||||
* may be renamed multiple times and may or may not conflict depending on the
|
||||
* order of the renames array.
|
||||
*
|
||||
* Returns true for success, false in error cases.
|
||||
*/
|
||||
bool qdict_rename_keys(QDict *qdict, const QDictRenames *renames, Error **errp)
|
||||
{
|
||||
QObject *qobj;
|
||||
|
||||
while (renames->from) {
|
||||
if (qdict_haskey(qdict, renames->from)) {
|
||||
if (qdict_haskey(qdict, renames->to)) {
|
||||
error_setg(errp, "'%s' and its alias '%s' can't be used at the "
|
||||
"same time", renames->to, renames->from);
|
||||
return false;
|
||||
}
|
||||
|
||||
qobj = qdict_get(qdict, renames->from);
|
||||
qobject_incref(qobj);
|
||||
qdict_put_obj(qdict, renames->to, qobj);
|
||||
qdict_del(qdict, renames->from);
|
||||
}
|
||||
|
||||
renames++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -665,6 +665,133 @@ static void qdict_crumple_test_empty(void)
|
|||
QDECREF(dst);
|
||||
}
|
||||
|
||||
static int qdict_count_entries(QDict *dict)
|
||||
{
|
||||
const QDictEntry *e;
|
||||
int count = 0;
|
||||
|
||||
for (e = qdict_first(dict); e; e = qdict_next(dict, e)) {
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void qdict_rename_keys_test(void)
|
||||
{
|
||||
QDict *dict = qdict_new();
|
||||
QDict *copy;
|
||||
QDictRenames *renames;
|
||||
Error *local_err = NULL;
|
||||
|
||||
qdict_put_str(dict, "abc", "foo");
|
||||
qdict_put_str(dict, "abcdef", "bar");
|
||||
qdict_put_int(dict, "number", 42);
|
||||
qdict_put_bool(dict, "flag", true);
|
||||
qdict_put_null(dict, "nothing");
|
||||
|
||||
/* Empty rename list */
|
||||
renames = (QDictRenames[]) {
|
||||
{ NULL, "this can be anything" }
|
||||
};
|
||||
copy = qdict_clone_shallow(dict);
|
||||
qdict_rename_keys(copy, renames, &error_abort);
|
||||
|
||||
g_assert_cmpstr(qdict_get_str(copy, "abc"), ==, "foo");
|
||||
g_assert_cmpstr(qdict_get_str(copy, "abcdef"), ==, "bar");
|
||||
g_assert_cmpint(qdict_get_int(copy, "number"), ==, 42);
|
||||
g_assert_cmpint(qdict_get_bool(copy, "flag"), ==, true);
|
||||
g_assert(qobject_type(qdict_get(copy, "nothing")) == QTYPE_QNULL);
|
||||
g_assert_cmpint(qdict_count_entries(copy), ==, 5);
|
||||
|
||||
QDECREF(copy);
|
||||
|
||||
/* Simple rename of all entries */
|
||||
renames = (QDictRenames[]) {
|
||||
{ "abc", "str1" },
|
||||
{ "abcdef", "str2" },
|
||||
{ "number", "int" },
|
||||
{ "flag", "bool" },
|
||||
{ "nothing", "null" },
|
||||
{ NULL , NULL }
|
||||
};
|
||||
copy = qdict_clone_shallow(dict);
|
||||
qdict_rename_keys(copy, renames, &error_abort);
|
||||
|
||||
g_assert(!qdict_haskey(copy, "abc"));
|
||||
g_assert(!qdict_haskey(copy, "abcdef"));
|
||||
g_assert(!qdict_haskey(copy, "number"));
|
||||
g_assert(!qdict_haskey(copy, "flag"));
|
||||
g_assert(!qdict_haskey(copy, "nothing"));
|
||||
|
||||
g_assert_cmpstr(qdict_get_str(copy, "str1"), ==, "foo");
|
||||
g_assert_cmpstr(qdict_get_str(copy, "str2"), ==, "bar");
|
||||
g_assert_cmpint(qdict_get_int(copy, "int"), ==, 42);
|
||||
g_assert_cmpint(qdict_get_bool(copy, "bool"), ==, true);
|
||||
g_assert(qobject_type(qdict_get(copy, "null")) == QTYPE_QNULL);
|
||||
g_assert_cmpint(qdict_count_entries(copy), ==, 5);
|
||||
|
||||
QDECREF(copy);
|
||||
|
||||
/* Renames are processed top to bottom */
|
||||
renames = (QDictRenames[]) {
|
||||
{ "abc", "tmp" },
|
||||
{ "abcdef", "abc" },
|
||||
{ "number", "abcdef" },
|
||||
{ "flag", "number" },
|
||||
{ "nothing", "flag" },
|
||||
{ "tmp", "nothing" },
|
||||
{ NULL , NULL }
|
||||
};
|
||||
copy = qdict_clone_shallow(dict);
|
||||
qdict_rename_keys(copy, renames, &error_abort);
|
||||
|
||||
g_assert_cmpstr(qdict_get_str(copy, "nothing"), ==, "foo");
|
||||
g_assert_cmpstr(qdict_get_str(copy, "abc"), ==, "bar");
|
||||
g_assert_cmpint(qdict_get_int(copy, "abcdef"), ==, 42);
|
||||
g_assert_cmpint(qdict_get_bool(copy, "number"), ==, true);
|
||||
g_assert(qobject_type(qdict_get(copy, "flag")) == QTYPE_QNULL);
|
||||
g_assert(!qdict_haskey(copy, "tmp"));
|
||||
g_assert_cmpint(qdict_count_entries(copy), ==, 5);
|
||||
|
||||
QDECREF(copy);
|
||||
|
||||
/* Conflicting rename */
|
||||
renames = (QDictRenames[]) {
|
||||
{ "abcdef", "abc" },
|
||||
{ NULL , NULL }
|
||||
};
|
||||
copy = qdict_clone_shallow(dict);
|
||||
qdict_rename_keys(copy, renames, &local_err);
|
||||
|
||||
g_assert(local_err != NULL);
|
||||
error_free(local_err);
|
||||
local_err = NULL;
|
||||
|
||||
g_assert_cmpstr(qdict_get_str(copy, "abc"), ==, "foo");
|
||||
g_assert_cmpstr(qdict_get_str(copy, "abcdef"), ==, "bar");
|
||||
g_assert_cmpint(qdict_get_int(copy, "number"), ==, 42);
|
||||
g_assert_cmpint(qdict_get_bool(copy, "flag"), ==, true);
|
||||
g_assert(qobject_type(qdict_get(copy, "nothing")) == QTYPE_QNULL);
|
||||
g_assert_cmpint(qdict_count_entries(copy), ==, 5);
|
||||
|
||||
QDECREF(copy);
|
||||
|
||||
/* Renames in an empty dict */
|
||||
renames = (QDictRenames[]) {
|
||||
{ "abcdef", "abc" },
|
||||
{ NULL , NULL }
|
||||
};
|
||||
|
||||
QDECREF(dict);
|
||||
dict = qdict_new();
|
||||
|
||||
qdict_rename_keys(dict, renames, &error_abort);
|
||||
g_assert(qdict_first(dict) == NULL);
|
||||
|
||||
QDECREF(dict);
|
||||
}
|
||||
|
||||
static void qdict_crumple_test_bad_inputs(void)
|
||||
{
|
||||
QDict *src;
|
||||
|
@ -880,6 +1007,8 @@ int main(int argc, char **argv)
|
|||
g_test_add_func("/public/crumple/bad_inputs",
|
||||
qdict_crumple_test_bad_inputs);
|
||||
|
||||
g_test_add_func("/public/rename_keys", qdict_rename_keys_test);
|
||||
|
||||
/* The Big one */
|
||||
if (g_test_slow()) {
|
||||
g_test_add_func("/stress/test", qdict_stress_test);
|
||||
|
|
|
@ -156,7 +156,7 @@ class TestSingleDrive(iotests.QMPTestCase):
|
|||
class TestParallelOps(iotests.QMPTestCase):
|
||||
num_ops = 4 # Number of parallel block-stream operations
|
||||
num_imgs = num_ops * 2 + 1
|
||||
image_len = num_ops * 1024 * 1024
|
||||
image_len = num_ops * 512 * 1024
|
||||
imgs = []
|
||||
|
||||
def setUp(self):
|
||||
|
@ -176,14 +176,14 @@ class TestParallelOps(iotests.QMPTestCase):
|
|||
'-o', 'backing_file=%s' % self.imgs[i-1], self.imgs[i])
|
||||
|
||||
# Put data into the images we are copying data from
|
||||
for i in range(self.num_imgs / 2):
|
||||
img_index = i * 2 + 1
|
||||
# Alternate between 512k and 1M.
|
||||
odd_img_indexes = [x for x in reversed(range(self.num_imgs)) if x % 2 == 1]
|
||||
for i in range(len(odd_img_indexes)):
|
||||
# Alternate between 256KB and 512KB.
|
||||
# This way jobs will not finish in the same order they were created
|
||||
num_kb = 512 + 512 * (i % 2)
|
||||
num_kb = 256 + 256 * (i % 2)
|
||||
qemu_io('-f', iotests.imgfmt,
|
||||
'-c', 'write -P %d %d %d' % (i, i*1024*1024, num_kb * 1024),
|
||||
self.imgs[img_index])
|
||||
'-c', 'write -P 0xFF %dk %dk' % (i * 512, num_kb),
|
||||
self.imgs[odd_img_indexes[i]])
|
||||
|
||||
# Attach the drive to the VM
|
||||
self.vm = iotests.VM()
|
||||
|
@ -318,12 +318,14 @@ class TestParallelOps(iotests.QMPTestCase):
|
|||
self.wait_until_completed(drive='commit-drive0')
|
||||
|
||||
# Test a block-stream and a block-commit job in parallel
|
||||
def test_stream_commit(self):
|
||||
# Here the stream job is supposed to finish quickly in order to reproduce
|
||||
# the scenario that triggers the bug fixed in 3d5d319e1221 and 1a63a907507
|
||||
def test_stream_commit_1(self):
|
||||
self.assertLessEqual(8, self.num_imgs)
|
||||
self.assert_no_active_block_jobs()
|
||||
|
||||
# Stream from node0 into node2
|
||||
result = self.vm.qmp('block-stream', device='node2', job_id='node2')
|
||||
result = self.vm.qmp('block-stream', device='node2', base_node='node0', job_id='node2')
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
# Commit from the active layer into node3
|
||||
|
@ -348,6 +350,38 @@ class TestParallelOps(iotests.QMPTestCase):
|
|||
|
||||
self.assert_no_active_block_jobs()
|
||||
|
||||
# This is similar to test_stream_commit_1 but both jobs are slowed
|
||||
# down so they can run in parallel for a little while.
|
||||
def test_stream_commit_2(self):
|
||||
self.assertLessEqual(8, self.num_imgs)
|
||||
self.assert_no_active_block_jobs()
|
||||
|
||||
# Stream from node0 into node4
|
||||
result = self.vm.qmp('block-stream', device='node4', base_node='node0', job_id='node4', speed=1024*1024)
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
# Commit from the active layer into node5
|
||||
result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[5], speed=1024*1024)
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
# Wait for all jobs to be finished.
|
||||
pending_jobs = ['node4', 'drive0']
|
||||
while len(pending_jobs) > 0:
|
||||
for event in self.vm.get_qmp_events(wait=True):
|
||||
if event['event'] == 'BLOCK_JOB_COMPLETED':
|
||||
node_name = self.dictpath(event, 'data/device')
|
||||
self.assertTrue(node_name in pending_jobs)
|
||||
self.assert_qmp_absent(event, 'data/error')
|
||||
pending_jobs.remove(node_name)
|
||||
if event['event'] == 'BLOCK_JOB_READY':
|
||||
self.assert_qmp(event, 'data/device', 'drive0')
|
||||
self.assert_qmp(event, 'data/type', 'commit')
|
||||
self.assert_qmp_absent(event, 'data/error')
|
||||
self.assertTrue('drive0' in pending_jobs)
|
||||
self.vm.qmp('block-job-complete', device='drive0')
|
||||
|
||||
self.assert_no_active_block_jobs()
|
||||
|
||||
# Test the base_node parameter
|
||||
def test_stream_base_node_name(self):
|
||||
self.assert_no_active_block_jobs()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.......................
|
||||
........................
|
||||
----------------------------------------------------------------------
|
||||
Ran 23 tests
|
||||
Ran 24 tests
|
||||
|
||||
OK
|
||||
|
|
|
@ -166,11 +166,11 @@ qemu-img create -f qcow2 -o compat=1.1 TEST_DIR/t.qcow2 64M
|
|||
Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat=1.1 cluster_size=65536 lazy_refcounts=off refcount_bits=16
|
||||
|
||||
qemu-img create -f qcow2 -o compat=0.42 TEST_DIR/t.qcow2 64M
|
||||
qemu-img: TEST_DIR/t.qcow2: Invalid compatibility level: '0.42'
|
||||
qemu-img: TEST_DIR/t.qcow2: Invalid parameter '0.42'
|
||||
Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat=0.42 cluster_size=65536 lazy_refcounts=off refcount_bits=16
|
||||
|
||||
qemu-img create -f qcow2 -o compat=foobar TEST_DIR/t.qcow2 64M
|
||||
qemu-img: TEST_DIR/t.qcow2: Invalid compatibility level: 'foobar'
|
||||
qemu-img: TEST_DIR/t.qcow2: Invalid parameter 'foobar'
|
||||
Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat=foobar cluster_size=65536 lazy_refcounts=off refcount_bits=16
|
||||
|
||||
== Check preallocation option ==
|
||||
|
@ -182,7 +182,7 @@ qemu-img create -f qcow2 -o preallocation=metadata TEST_DIR/t.qcow2 64M
|
|||
Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 cluster_size=65536 preallocation=metadata lazy_refcounts=off refcount_bits=16
|
||||
|
||||
qemu-img create -f qcow2 -o preallocation=1234 TEST_DIR/t.qcow2 64M
|
||||
qemu-img: TEST_DIR/t.qcow2: invalid parameter value: 1234
|
||||
qemu-img: TEST_DIR/t.qcow2: Invalid parameter '1234'
|
||||
Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 cluster_size=65536 preallocation=1234 lazy_refcounts=off refcount_bits=16
|
||||
|
||||
== Check encryption option ==
|
||||
|
@ -205,7 +205,7 @@ qemu-img create -f qcow2 -o compat=0.10,lazy_refcounts=off TEST_DIR/t.qcow2 64M
|
|||
Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat=0.10 cluster_size=65536 lazy_refcounts=off refcount_bits=16
|
||||
|
||||
qemu-img create -f qcow2 -o compat=0.10,lazy_refcounts=on TEST_DIR/t.qcow2 64M
|
||||
qemu-img: TEST_DIR/t.qcow2: Lazy refcounts only supported with compatibility level 1.1 and above (use compat=1.1 or greater)
|
||||
qemu-img: TEST_DIR/t.qcow2: Lazy refcounts only supported with compatibility level 1.1 and above (use version=v3 or greater)
|
||||
Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat=0.10 cluster_size=65536 lazy_refcounts=on refcount_bits=16
|
||||
|
||||
*** done
|
||||
|
|
|
@ -152,9 +152,8 @@ done
|
|||
echo
|
||||
echo "=== Testing afl image with a very large capacity ==="
|
||||
_use_sample_img afl9.vmdk.bz2
|
||||
# The sed makes this test pass on machines with little RAM
|
||||
# (and also with 32 bit builds)
|
||||
_img_info | sed -e 's/Cannot allocate memory/Invalid argument/'
|
||||
_img_info | grep -q 'Cannot allocate memory' && _notrun "Insufficent memory, skipped test"
|
||||
_img_info
|
||||
_cleanup_test_img
|
||||
|
||||
# success, all done
|
||||
|
|
|
@ -171,12 +171,32 @@ poke_file "$TEST_IMG" "$offset_l2_table_0" "\x80\x00\x00\xff\xff\xff\x00\x00"
|
|||
{ $QEMU_IMG snapshot -c test $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
||||
|
||||
echo
|
||||
echo "== Invalid snapshot L1 table =="
|
||||
echo "== Invalid snapshot L1 table offset =="
|
||||
_make_test_img 64M
|
||||
{ $QEMU_IO -c "write 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
||||
{ $QEMU_IMG snapshot -c test $TEST_IMG; } 2>&1 | _filter_testdir
|
||||
poke_file "$TEST_IMG" "$offset_snap1_l1_offset" "\x00\x00\x00\x00\x00\x40\x02\x00"
|
||||
{ $QEMU_IMG convert -s test $TEST_IMG $TEST_IMG.snap; } 2>&1 | _filter_testdir
|
||||
{ $QEMU_IMG amend -o compat=0.10 $TEST_IMG; } 2>&1 | _filter_testdir
|
||||
{ $QEMU_IO -c "open -o overlap-check.inactive-l2=on $TEST_IMG" \
|
||||
-c 'write 0 4k'; } 2>&1 | _filter_qemu_io | _filter_testdir
|
||||
{ $QEMU_IMG snapshot -a test $TEST_IMG; } 2>&1 | _filter_testdir
|
||||
{ $QEMU_IMG snapshot -d test $TEST_IMG; } 2>&1 | _filter_testdir
|
||||
_check_test_img
|
||||
|
||||
echo
|
||||
echo "== Invalid snapshot L1 table size =="
|
||||
_make_test_img 64M
|
||||
{ $QEMU_IO -c "write 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
|
||||
{ $QEMU_IMG snapshot -c test $TEST_IMG; } 2>&1 | _filter_testdir
|
||||
poke_file "$TEST_IMG" "$offset_snap1_l1_size" "\x10\x00\x00\x00"
|
||||
{ $QEMU_IMG convert -s test $TEST_IMG $TEST_IMG.snap; } 2>&1 | _filter_testdir
|
||||
{ $QEMU_IMG amend -o compat=0.10 $TEST_IMG; } 2>&1 | _filter_testdir
|
||||
{ $QEMU_IO -c "open -o overlap-check.inactive-l2=on $TEST_IMG" \
|
||||
-c 'write 0 4k'; } 2>&1 | _filter_qemu_io | _filter_testdir
|
||||
{ $QEMU_IMG snapshot -a test $TEST_IMG; } 2>&1 | _filter_testdir
|
||||
{ $QEMU_IMG snapshot -d test $TEST_IMG; } 2>&1 | _filter_testdir
|
||||
_check_test_img
|
||||
|
||||
# success, all done
|
||||
echo "*** done"
|
||||
|
|
|
@ -18,18 +18,18 @@ can't open device TEST_DIR/t.qcow2: Reference count table too large
|
|||
|
||||
== Misaligned refcount table ==
|
||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||
can't open device TEST_DIR/t.qcow2: Invalid reference count table offset
|
||||
can't open device TEST_DIR/t.qcow2: Reference count table offset invalid
|
||||
|
||||
== Huge refcount offset ==
|
||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||
can't open device TEST_DIR/t.qcow2: Invalid reference count table offset
|
||||
can't open device TEST_DIR/t.qcow2: Reference count table offset invalid
|
||||
|
||||
== Invalid snapshot table ==
|
||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||
can't open device TEST_DIR/t.qcow2: Too many snapshots
|
||||
can't open device TEST_DIR/t.qcow2: Too many snapshots
|
||||
can't open device TEST_DIR/t.qcow2: Invalid snapshot table offset
|
||||
can't open device TEST_DIR/t.qcow2: Invalid snapshot table offset
|
||||
can't open device TEST_DIR/t.qcow2: Snapshot table too large
|
||||
can't open device TEST_DIR/t.qcow2: Snapshot table too large
|
||||
can't open device TEST_DIR/t.qcow2: Snapshot table offset invalid
|
||||
can't open device TEST_DIR/t.qcow2: Snapshot table offset invalid
|
||||
|
||||
== Hitting snapshot table size limit ==
|
||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||
|
@ -41,8 +41,8 @@ read 512/512 bytes at offset 0
|
|||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||
can't open device TEST_DIR/t.qcow2: Active L1 table too large
|
||||
can't open device TEST_DIR/t.qcow2: Active L1 table too large
|
||||
can't open device TEST_DIR/t.qcow2: Invalid L1 table offset
|
||||
can't open device TEST_DIR/t.qcow2: Invalid L1 table offset
|
||||
can't open device TEST_DIR/t.qcow2: Active L1 table offset invalid
|
||||
can't open device TEST_DIR/t.qcow2: Active L1 table offset invalid
|
||||
|
||||
== Invalid L1 table (with internal snapshot in the image) ==
|
||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||
|
@ -59,9 +59,49 @@ wrote 512/512 bytes at offset 0
|
|||
qemu-img: Could not create snapshot 'test': -27 (File too large)
|
||||
qemu-img: Could not create snapshot 'test': -11 (Resource temporarily unavailable)
|
||||
|
||||
== Invalid snapshot L1 table ==
|
||||
== Invalid snapshot L1 table offset ==
|
||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||
wrote 512/512 bytes at offset 0
|
||||
512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
qemu-img: Failed to load snapshot: Snapshot L1 table offset invalid
|
||||
qemu-img: Snapshot L1 table offset invalid
|
||||
qemu-img: Error while amending options: Invalid argument
|
||||
Failed to flush the refcount block cache: Invalid argument
|
||||
write failed: Invalid argument
|
||||
qemu-img: Snapshot L1 table offset invalid
|
||||
qemu-img: Could not apply snapshot 'test': Failed to load snapshot: Invalid argument
|
||||
qemu-img: Could not delete snapshot 'test': Snapshot L1 table offset invalid
|
||||
ERROR snapshot 1 (test) l1_offset=0x400200: L1 table is not cluster aligned; snapshot table entry corrupted
|
||||
Leaked cluster 4 refcount=2 reference=1
|
||||
Leaked cluster 5 refcount=2 reference=1
|
||||
Leaked cluster 6 refcount=1 reference=0
|
||||
|
||||
1 errors were found on the image.
|
||||
Data may be corrupted, or further writes to the image may corrupt it.
|
||||
|
||||
3 leaked clusters were found on the image.
|
||||
This means waste of disk space, but no harm to data.
|
||||
|
||||
== Invalid snapshot L1 table size ==
|
||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||
wrote 512/512 bytes at offset 0
|
||||
512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
qemu-img: Failed to load snapshot: Snapshot L1 table too large
|
||||
qemu-img: Snapshot L1 table too large
|
||||
qemu-img: Error while amending options: File too large
|
||||
Failed to flush the refcount block cache: File too large
|
||||
write failed: File too large
|
||||
qemu-img: Snapshot L1 table too large
|
||||
qemu-img: Could not apply snapshot 'test': Failed to load snapshot: File too large
|
||||
qemu-img: Could not delete snapshot 'test': Snapshot L1 table too large
|
||||
ERROR snapshot 1 (test) l1_size=0x10000000: L1 table is too large; snapshot table entry corrupted
|
||||
Leaked cluster 4 refcount=2 reference=1
|
||||
Leaked cluster 5 refcount=2 reference=1
|
||||
Leaked cluster 6 refcount=1 reference=0
|
||||
|
||||
1 errors were found on the image.
|
||||
Data may be corrupted, or further writes to the image may corrupt it.
|
||||
|
||||
3 leaked clusters were found on the image.
|
||||
This means waste of disk space, but no harm to data.
|
||||
*** done
|
||||
|
|
|
@ -21,9 +21,9 @@ refcount bits: 16
|
|||
|
||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||
refcount bits: 16
|
||||
qemu-img: TEST_DIR/t.IMGFMT: Different refcount widths than 16 bits require compatibility level 1.1 or above (use or greater)
|
||||
qemu-img: TEST_DIR/t.IMGFMT: Different refcount widths than 16 bits require compatibility level 1.1 or above (use version=v3 or greater)
|
||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||
qemu-img: TEST_DIR/t.IMGFMT: Different refcount widths than 16 bits require compatibility level 1.1 or above (use or greater)
|
||||
qemu-img: TEST_DIR/t.IMGFMT: Different refcount widths than 16 bits require compatibility level 1.1 or above (use version=v3 or greater)
|
||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||
|
||||
=== Snapshot limit on refcount_bits=1 ===
|
||||
|
|
|
@ -32,6 +32,7 @@ _cleanup()
|
|||
{
|
||||
_cleanup_test_img
|
||||
rm -f "${TEST_IMG}.base"
|
||||
rm -f "${TEST_IMG}.overlay"
|
||||
rm -f "${TEST_IMG}.convert"
|
||||
rm -f "${TEST_IMG}.a"
|
||||
rm -f "${TEST_IMG}.b"
|
||||
|
@ -177,8 +178,6 @@ 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 "== Closing an image should unlock it =="
|
||||
_launch_qemu
|
||||
|
||||
_send_qemu_cmd $QEMU_HANDLE \
|
||||
|
@ -193,7 +192,10 @@ _send_qemu_cmd $QEMU_HANDLE \
|
|||
|
||||
_run_cmd $QEMU_IO "${TEST_IMG}" -c 'write 0 512'
|
||||
|
||||
echo "Closing drive"
|
||||
echo "Creating overlay with qemu-img when the guest is running should be allowed"
|
||||
_run_cmd $QEMU_IMG create -f $IMGFMT -b "${TEST_IMG}" "${TEST_IMG}.overlay"
|
||||
|
||||
echo "== Closing an image should unlock it =="
|
||||
_send_qemu_cmd $QEMU_HANDLE \
|
||||
"{ 'execute': 'human-monitor-command',
|
||||
'arguments': { 'command-line': 'drive_del d0' } }" \
|
||||
|
|
|
@ -372,15 +372,16 @@ 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?
|
||||
|
||||
== Closing an image should unlock it ==
|
||||
{"return": {}}
|
||||
Adding drive
|
||||
|
||||
_qemu_io_wrapper TEST_DIR/t.qcow2 -c write 0 512
|
||||
can't open device TEST_DIR/t.qcow2: Failed to get "write" lock
|
||||
Is another process using the image?
|
||||
Closing drive
|
||||
Creating overlay with qemu-img when the guest is running should be allowed
|
||||
|
||||
_qemu_img_wrapper create -f qcow2 -b TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.overlay
|
||||
== Closing an image should unlock it ==
|
||||
|
||||
_qemu_io_wrapper TEST_DIR/t.qcow2 -c write 0 512
|
||||
Adding two and closing one
|
||||
|
|
|
@ -49,11 +49,18 @@ with iotests.FilePath('disk0.img') as disk0_img_path, \
|
|||
node_name='drive1-node', iothread='iothread0',
|
||||
force=True))
|
||||
|
||||
iotests.log('Enabling migration QMP events...')
|
||||
iotests.log(vm.qmp('migrate-set-capabilities', capabilities=[
|
||||
{
|
||||
'capability': 'events',
|
||||
'state': True
|
||||
}
|
||||
]))
|
||||
|
||||
iotests.log('Starting migration...')
|
||||
iotests.log(vm.qmp('migrate', uri='exec:cat >/dev/null'))
|
||||
while True:
|
||||
vm.get_qmp_event(wait=60.0)
|
||||
result = vm.qmp('query-migrate')
|
||||
status = result.get('return', {}).get('status', None)
|
||||
if status == 'completed':
|
||||
event = vm.event_wait('MIGRATION')
|
||||
iotests.log(event, filters=[iotests.filter_qmp_event])
|
||||
if event['data']['status'] == 'completed':
|
||||
break
|
||||
|
|
|
@ -2,5 +2,10 @@ Launching VM...
|
|||
Setting IOThreads...
|
||||
{u'return': {}}
|
||||
{u'return': {}}
|
||||
Enabling migration QMP events...
|
||||
{u'return': {}}
|
||||
Starting migration...
|
||||
{u'return': {}}
|
||||
{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'setup'}, u'event': u'MIGRATION'}
|
||||
{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'active'}, u'event': u'MIGRATION'}
|
||||
{u'timestamp': {u'seconds': 'SECS', u'microseconds': 'USECS'}, u'data': {u'status': u'completed'}, u'event': u'MIGRATION'}
|
||||
|
|
|
@ -0,0 +1,436 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Test qcow2 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 qcow2
|
||||
_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 <<EOF
|
||||
{ "execute": "qmp_capabilities" }
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "file",
|
||||
"filename": "$TEST_IMG",
|
||||
"size": 0
|
||||
}
|
||||
}
|
||||
{ "execute": "blockdev-add",
|
||||
"arguments": {
|
||||
"driver": "file",
|
||||
"node-name": "imgfile",
|
||||
"filename": "$TEST_IMG"
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "imgfile",
|
||||
"size": $size
|
||||
}
|
||||
}
|
||||
{ "execute": "quit" }
|
||||
EOF
|
||||
|
||||
_img_info --format-specific
|
||||
|
||||
echo
|
||||
echo "=== Successful image creation (inline blockdev-add, explicit defaults) ==="
|
||||
echo
|
||||
|
||||
# Choose a different size to show that we got a new image
|
||||
size=$((64 * 1024 * 1024))
|
||||
|
||||
run_qemu <<EOF
|
||||
{ "execute": "qmp_capabilities" }
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "file",
|
||||
"filename": "$TEST_IMG",
|
||||
"size": 0,
|
||||
"preallocation": "off",
|
||||
"nocow": false
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": {
|
||||
"driver": "file",
|
||||
"filename": "$TEST_IMG"
|
||||
},
|
||||
"size": $size,
|
||||
"version": "v3",
|
||||
"cluster-size": 65536,
|
||||
"preallocation": "off",
|
||||
"lazy-refcounts": false,
|
||||
"refcount-bits": 16
|
||||
}
|
||||
}
|
||||
{ "execute": "quit" }
|
||||
EOF
|
||||
|
||||
_img_info --format-specific
|
||||
|
||||
echo
|
||||
echo "=== Successful image creation (v3 non-default options) ==="
|
||||
echo
|
||||
|
||||
# Choose a different size to show that we got a new image
|
||||
size=$((32 * 1024 * 1024))
|
||||
|
||||
run_qemu <<EOF
|
||||
{ "execute": "qmp_capabilities" }
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "file",
|
||||
"filename": "$TEST_IMG",
|
||||
"size": 0,
|
||||
"preallocation": "falloc",
|
||||
"nocow": true
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": {
|
||||
"driver": "file",
|
||||
"filename": "$TEST_IMG"
|
||||
},
|
||||
"size": $size,
|
||||
"version": "v3",
|
||||
"cluster-size": 2097152,
|
||||
"preallocation": "metadata",
|
||||
"lazy-refcounts": true,
|
||||
"refcount-bits": 1
|
||||
}
|
||||
}
|
||||
{ "execute": "quit" }
|
||||
EOF
|
||||
|
||||
_img_info --format-specific
|
||||
|
||||
echo
|
||||
echo "=== Successful image creation (v2 non-default options) ==="
|
||||
echo
|
||||
|
||||
mv $TEST_IMG $TEST_IMG.base
|
||||
|
||||
run_qemu <<EOF
|
||||
{ "execute": "qmp_capabilities" }
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "file",
|
||||
"filename": "$TEST_IMG",
|
||||
"size": 0
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": {
|
||||
"driver": "file",
|
||||
"filename": "$TEST_IMG"
|
||||
},
|
||||
"size": $size,
|
||||
"backing-file": "$TEST_IMG.base",
|
||||
"backing-fmt": "qcow2",
|
||||
"version": "v2",
|
||||
"cluster-size": 512
|
||||
}
|
||||
}
|
||||
{ "execute": "quit" }
|
||||
EOF
|
||||
|
||||
_img_info --format-specific
|
||||
|
||||
echo
|
||||
echo "=== Successful image creation (encrypted) ==="
|
||||
echo
|
||||
|
||||
run_qemu -object secret,id=keysec0,data="foo" <<EOF
|
||||
{ "execute": "qmp_capabilities" }
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": {
|
||||
"driver": "file",
|
||||
"filename": "$TEST_IMG"
|
||||
},
|
||||
"size": $size,
|
||||
"encrypt": {
|
||||
"format": "luks",
|
||||
"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 "=== 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. Misaligned image size
|
||||
# 2. 2^64 - 512
|
||||
# 3. 2^63 = 8 EB (qemu-img enforces image sizes less than this)
|
||||
# 4. 2^63 - 512 (generally valid, but qcow2 can't handle images this size)
|
||||
|
||||
run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF
|
||||
{ "execute": "qmp_capabilities" }
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 1234
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 18446744073709551104
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 9223372036854775808
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 9223372036854775296
|
||||
}
|
||||
}
|
||||
{ "execute": "quit" }
|
||||
EOF
|
||||
|
||||
echo
|
||||
echo "=== Invalid version ==="
|
||||
echo
|
||||
|
||||
run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF
|
||||
{ "execute": "qmp_capabilities" }
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 67108864,
|
||||
"version": "v1"
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 67108864,
|
||||
"version": "v2",
|
||||
"lazy-refcounts": true
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 67108864,
|
||||
"version": "v2",
|
||||
"refcount-bits": 8
|
||||
}
|
||||
}
|
||||
{ "execute": "quit" }
|
||||
EOF
|
||||
|
||||
echo
|
||||
echo "=== Invalid backing file options ==="
|
||||
echo
|
||||
|
||||
run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF
|
||||
{ "execute": "qmp_capabilities" }
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 67108864,
|
||||
"backing-file": "/dev/null",
|
||||
"preallocation": "full"
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 67108864,
|
||||
"backing-fmt": "$IMGFMT"
|
||||
}
|
||||
}
|
||||
{ "execute": "quit" }
|
||||
EOF
|
||||
|
||||
echo
|
||||
echo "=== Invalid cluster size ==="
|
||||
echo
|
||||
|
||||
run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF
|
||||
{ "execute": "qmp_capabilities" }
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 67108864,
|
||||
"cluster-size": 1234
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 67108864,
|
||||
"cluster-size": 128
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 67108864,
|
||||
"cluster-size": 4194304
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 67108864,
|
||||
"cluster-size": 0
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 281474976710656,
|
||||
"cluster-size": 512
|
||||
}
|
||||
}
|
||||
{ "execute": "quit" }
|
||||
EOF
|
||||
|
||||
echo
|
||||
echo "=== Invalid refcount width ==="
|
||||
echo
|
||||
|
||||
run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF
|
||||
{ "execute": "qmp_capabilities" }
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 67108864,
|
||||
"refcount-bits": 128
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 67108864,
|
||||
"refcount-bits": 0
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "$IMGFMT",
|
||||
"file": "node0",
|
||||
"size": 67108864,
|
||||
"refcount-bits": 7
|
||||
}
|
||||
}
|
||||
{ "execute": "quit" }
|
||||
EOF
|
||||
|
||||
# success, all done
|
||||
echo "*** done"
|
||||
rm -f $seq.full
|
||||
status=0
|
|
@ -0,0 +1,209 @@
|
|||
QA output created by 206
|
||||
|
||||
=== Successful image creation (defaults) ===
|
||||
|
||||
Testing:
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
|
||||
image: TEST_DIR/t.IMGFMT
|
||||
file format: IMGFMT
|
||||
virtual size: 128M (134217728 bytes)
|
||||
cluster_size: 65536
|
||||
Format specific information:
|
||||
compat: 1.1
|
||||
lazy refcounts: false
|
||||
refcount bits: 16
|
||||
corrupt: false
|
||||
|
||||
=== Successful image creation (inline blockdev-add, explicit defaults) ===
|
||||
|
||||
Testing:
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
|
||||
image: TEST_DIR/t.IMGFMT
|
||||
file format: IMGFMT
|
||||
virtual size: 64M (67108864 bytes)
|
||||
cluster_size: 65536
|
||||
Format specific information:
|
||||
compat: 1.1
|
||||
lazy refcounts: false
|
||||
refcount bits: 16
|
||||
corrupt: false
|
||||
|
||||
=== Successful image creation (v3 non-default options) ===
|
||||
|
||||
Testing:
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
|
||||
image: TEST_DIR/t.IMGFMT
|
||||
file format: IMGFMT
|
||||
virtual size: 32M (33554432 bytes)
|
||||
cluster_size: 2097152
|
||||
Format specific information:
|
||||
compat: 1.1
|
||||
lazy refcounts: true
|
||||
refcount bits: 1
|
||||
corrupt: false
|
||||
|
||||
=== Successful image creation (v2 non-default options) ===
|
||||
|
||||
Testing:
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
|
||||
image: TEST_DIR/t.IMGFMT
|
||||
file format: IMGFMT
|
||||
virtual size: 32M (33554432 bytes)
|
||||
cluster_size: 512
|
||||
backing file: TEST_DIR/t.IMGFMT.base
|
||||
backing file format: IMGFMT
|
||||
Format specific information:
|
||||
compat: 0.10
|
||||
refcount bits: 16
|
||||
|
||||
=== Successful image creation (encrypted) ===
|
||||
|
||||
Testing: -object secret,id=keysec0,data=foo
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
|
||||
image: TEST_DIR/t.IMGFMT
|
||||
file format: IMGFMT
|
||||
virtual size: 32M (33554432 bytes)
|
||||
Format specific information:
|
||||
compat: 1.1
|
||||
lazy refcounts: false
|
||||
refcount bits: 16
|
||||
encrypt:
|
||||
ivgen alg: plain64
|
||||
hash alg: sha1
|
||||
cipher alg: twofish-128
|
||||
uuid: 00000000-0000-0000-0000-000000000000
|
||||
format: luks
|
||||
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
|
||||
corrupt: false
|
||||
|
||||
=== 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}}
|
||||
|
||||
|
||||
=== Invalid sizes ===
|
||||
|
||||
Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"error": {"class": "GenericError", "desc": "Image size must be a multiple of 512 bytes"}}
|
||||
{"error": {"class": "GenericError", "desc": "Could not resize image: Image size cannot be negative"}}
|
||||
{"error": {"class": "GenericError", "desc": "Could not resize image: Image size cannot be negative"}}
|
||||
{"error": {"class": "GenericError", "desc": "Could not resize image: Failed to grow the L1 table: File too large"}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
|
||||
|
||||
=== Invalid version ===
|
||||
|
||||
Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"error": {"class": "GenericError", "desc": "Invalid parameter 'v1'"}}
|
||||
{"error": {"class": "GenericError", "desc": "Lazy refcounts only supported with compatibility level 1.1 and above (use version=v3 or greater)"}}
|
||||
{"error": {"class": "GenericError", "desc": "Different refcount widths than 16 bits require compatibility level 1.1 or above (use version=v3 or greater)"}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
|
||||
|
||||
=== Invalid backing file options ===
|
||||
|
||||
Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"error": {"class": "GenericError", "desc": "Backing file and preallocation cannot be used at the same time"}}
|
||||
{"error": {"class": "GenericError", "desc": "Backing format cannot be used without backing file"}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
|
||||
|
||||
=== Invalid cluster size ===
|
||||
|
||||
Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"error": {"class": "GenericError", "desc": "Cluster size must be a power of two between 512 and 2048k"}}
|
||||
{"error": {"class": "GenericError", "desc": "Cluster size must be a power of two between 512 and 2048k"}}
|
||||
{"error": {"class": "GenericError", "desc": "Cluster size must be a power of two between 512 and 2048k"}}
|
||||
{"error": {"class": "GenericError", "desc": "Cluster size must be a power of two between 512 and 2048k"}}
|
||||
{"error": {"class": "GenericError", "desc": "Could not resize image: Failed to grow the L1 table: File too large"}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
|
||||
|
||||
=== Invalid refcount width ===
|
||||
|
||||
Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"error": {"class": "GenericError", "desc": "Refcount width must be a power of two and may not exceed 64 bits"}}
|
||||
{"error": {"class": "GenericError", "desc": "Refcount width must be a power of two and may not exceed 64 bits"}}
|
||||
{"error": {"class": "GenericError", "desc": "Refcount width must be a power of two and may not exceed 64 bits"}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
|
||||
*** done
|
|
@ -0,0 +1,261 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Test ssh 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 raw
|
||||
_supported_proto ssh
|
||||
_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
|
||||
|
||||
run_qemu <<EOF
|
||||
{ "execute": "qmp_capabilities" }
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "ssh",
|
||||
"location": {
|
||||
"path": "$TEST_IMG_FILE",
|
||||
"server": {
|
||||
"host": "127.0.0.1",
|
||||
"port": "22"
|
||||
}
|
||||
},
|
||||
"size": 4194304
|
||||
}
|
||||
}
|
||||
{ "execute": "quit" }
|
||||
EOF
|
||||
|
||||
_img_info | _filter_img_info
|
||||
echo
|
||||
TEST_IMG=$TEST_IMG_FILE _img_info | _filter_img_info
|
||||
|
||||
echo
|
||||
echo "=== Test host-key-check options ==="
|
||||
echo
|
||||
|
||||
run_qemu <<EOF
|
||||
{ "execute": "qmp_capabilities" }
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "ssh",
|
||||
"location": {
|
||||
"path": "$TEST_IMG_FILE",
|
||||
"server": {
|
||||
"host": "127.0.0.1",
|
||||
"port": "22"
|
||||
},
|
||||
"host-key-check": {
|
||||
"mode": "none"
|
||||
}
|
||||
},
|
||||
"size": 8388608
|
||||
}
|
||||
}
|
||||
{ "execute": "quit" }
|
||||
EOF
|
||||
|
||||
_img_info | _filter_img_info
|
||||
|
||||
run_qemu <<EOF
|
||||
{ "execute": "qmp_capabilities" }
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "ssh",
|
||||
"location": {
|
||||
"path": "$TEST_IMG_FILE",
|
||||
"server": {
|
||||
"host": "127.0.0.1",
|
||||
"port": "22"
|
||||
},
|
||||
"host-key-check": {
|
||||
"mode": "known_hosts"
|
||||
}
|
||||
},
|
||||
"size": 4194304
|
||||
}
|
||||
}
|
||||
{ "execute": "quit" }
|
||||
EOF
|
||||
|
||||
_img_info | _filter_img_info
|
||||
|
||||
|
||||
key=$(ssh-keyscan -t rsa 127.0.0.1 2>/dev/null | grep -v "\\^#" |
|
||||
cut -d" " -f3 | base64 -d | md5sum -b | cut -d" " -f1)
|
||||
|
||||
run_qemu <<EOF
|
||||
{ "execute": "qmp_capabilities" }
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "ssh",
|
||||
"location": {
|
||||
"path": "$TEST_IMG_FILE",
|
||||
"server": {
|
||||
"host": "127.0.0.1",
|
||||
"port": "22"
|
||||
},
|
||||
"host-key-check": {
|
||||
"mode": "hash",
|
||||
"type": "md5",
|
||||
"hash": "wrong"
|
||||
}
|
||||
},
|
||||
"size": 8388608
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "ssh",
|
||||
"location": {
|
||||
"path": "$TEST_IMG_FILE",
|
||||
"server": {
|
||||
"host": "127.0.0.1",
|
||||
"port": "22"
|
||||
},
|
||||
"host-key-check": {
|
||||
"mode": "hash",
|
||||
"type": "md5",
|
||||
"hash": "$key"
|
||||
}
|
||||
},
|
||||
"size": 8388608
|
||||
}
|
||||
}
|
||||
{ "execute": "quit" }
|
||||
EOF
|
||||
|
||||
_img_info | _filter_img_info
|
||||
|
||||
|
||||
key=$(ssh-keyscan -t rsa 127.0.0.1 2>/dev/null | grep -v "\\^#" |
|
||||
cut -d" " -f3 | base64 -d | sha1sum -b | cut -d" " -f1)
|
||||
|
||||
run_qemu <<EOF
|
||||
{ "execute": "qmp_capabilities" }
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "ssh",
|
||||
"location": {
|
||||
"path": "$TEST_IMG_FILE",
|
||||
"server": {
|
||||
"host": "127.0.0.1",
|
||||
"port": "22"
|
||||
},
|
||||
"host-key-check": {
|
||||
"mode": "hash",
|
||||
"type": "sha1",
|
||||
"hash": "wrong"
|
||||
}
|
||||
},
|
||||
"size": 4194304
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "ssh",
|
||||
"location": {
|
||||
"path": "$TEST_IMG_FILE",
|
||||
"server": {
|
||||
"host": "127.0.0.1",
|
||||
"port": "22"
|
||||
},
|
||||
"host-key-check": {
|
||||
"mode": "hash",
|
||||
"type": "sha1",
|
||||
"hash": "$key"
|
||||
}
|
||||
},
|
||||
"size": 4194304
|
||||
}
|
||||
}
|
||||
{ "execute": "quit" }
|
||||
EOF
|
||||
|
||||
_img_info | _filter_img_info
|
||||
|
||||
echo
|
||||
echo "=== Invalid path and user ==="
|
||||
echo
|
||||
|
||||
run_qemu <<EOF
|
||||
{ "execute": "qmp_capabilities" }
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "ssh",
|
||||
"location": {
|
||||
"path": "/this/is/not/an/existing/path",
|
||||
"server": {
|
||||
"host": "127.0.0.1",
|
||||
"port": "22"
|
||||
}
|
||||
},
|
||||
"size": 4194304
|
||||
}
|
||||
}
|
||||
{ "execute": "x-blockdev-create",
|
||||
"arguments": {
|
||||
"driver": "ssh",
|
||||
"location": {
|
||||
"path": "$TEST_IMG_FILE",
|
||||
"user": "invalid user",
|
||||
"server": {
|
||||
"host": "127.0.0.1",
|
||||
"port": "22"
|
||||
}
|
||||
},
|
||||
"size": 4194304
|
||||
}
|
||||
}
|
||||
{ "execute": "quit" }
|
||||
EOF
|
||||
|
||||
# success, all done
|
||||
echo "*** done"
|
||||
rm -f $seq.full
|
||||
status=0
|
|
@ -0,0 +1,75 @@
|
|||
QA output created by 207
|
||||
|
||||
=== Successful image creation (defaults) ===
|
||||
|
||||
Testing:
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
|
||||
image: json:{"driver": "IMGFMT", "file": {"server.host": "127.0.0.1", "server.port": "22", "driver": "ssh", "path": "TEST_DIR/t.IMGFMT"}}
|
||||
file format: IMGFMT
|
||||
virtual size: 4.0M (4194304 bytes)
|
||||
|
||||
image: TEST_DIR/t.IMGFMT
|
||||
file format: IMGFMT
|
||||
virtual size: 4.0M (4194304 bytes)
|
||||
|
||||
=== Test host-key-check options ===
|
||||
|
||||
Testing:
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
|
||||
image: json:{"driver": "IMGFMT", "file": {"server.host": "127.0.0.1", "server.port": "22", "driver": "ssh", "path": "TEST_DIR/t.IMGFMT"}}
|
||||
file format: IMGFMT
|
||||
virtual size: 8.0M (8388608 bytes)
|
||||
Testing:
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
|
||||
image: json:{"driver": "IMGFMT", "file": {"server.host": "127.0.0.1", "server.port": "22", "driver": "ssh", "path": "TEST_DIR/t.IMGFMT"}}
|
||||
file format: IMGFMT
|
||||
virtual size: 4.0M (4194304 bytes)
|
||||
Testing:
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"error": {"class": "GenericError", "desc": "remote host key does not match host_key_check 'wrong'"}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
|
||||
image: json:{"driver": "IMGFMT", "file": {"server.host": "127.0.0.1", "server.port": "22", "driver": "ssh", "path": "TEST_DIR/t.IMGFMT"}}
|
||||
file format: IMGFMT
|
||||
virtual size: 8.0M (8388608 bytes)
|
||||
Testing:
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"error": {"class": "GenericError", "desc": "remote host key does not match host_key_check 'wrong'"}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
|
||||
image: json:{"driver": "IMGFMT", "file": {"server.host": "127.0.0.1", "server.port": "22", "driver": "ssh", "path": "TEST_DIR/t.IMGFMT"}}
|
||||
file format: IMGFMT
|
||||
virtual size: 4.0M (4194304 bytes)
|
||||
|
||||
=== Invalid path and user ===
|
||||
|
||||
Testing:
|
||||
QMP_VERSION
|
||||
{"return": {}}
|
||||
{"error": {"class": "GenericError", "desc": "failed to open remote file '/this/is/not/an/existing/path': Failed opening remote file (libssh2 error code: -31)"}}
|
||||
{"error": {"class": "GenericError", "desc": "failed to authenticate using publickey authentication and the identities held by your ssh-agent"}}
|
||||
{"return": {}}
|
||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
|
||||
|
||||
*** done
|
|
@ -202,3 +202,5 @@
|
|||
203 rw auto
|
||||
204 rw auto quick
|
||||
205 rw auto quick
|
||||
206 rw auto
|
||||
207 rw auto
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "qemu/osdep.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qemu/option.h"
|
||||
#include "qemu/option_int.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
|
@ -23,6 +24,8 @@ static QemuOptsList opts_list_01 = {
|
|||
{
|
||||
.name = "str1",
|
||||
.type = QEMU_OPT_STRING,
|
||||
.help = "Help texts are preserved in qemu_opts_append",
|
||||
.def_value_str = "default",
|
||||
},{
|
||||
.name = "str2",
|
||||
.type = QEMU_OPT_STRING,
|
||||
|
@ -32,6 +35,7 @@ static QemuOptsList opts_list_01 = {
|
|||
},{
|
||||
.name = "number1",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
.help = "Having help texts only for some options is okay",
|
||||
},{
|
||||
.name = "number2",
|
||||
.type = QEMU_OPT_NUMBER,
|
||||
|
@ -743,6 +747,250 @@ static void test_opts_parse_size(void)
|
|||
qemu_opts_reset(&opts_list_02);
|
||||
}
|
||||
|
||||
static void append_verify_list_01(QemuOptDesc *desc, bool with_overlapping)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (with_overlapping) {
|
||||
g_assert_cmpstr(desc[i].name, ==, "str1");
|
||||
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_STRING);
|
||||
g_assert_cmpstr(desc[i].help, ==,
|
||||
"Help texts are preserved in qemu_opts_append");
|
||||
g_assert_cmpstr(desc[i].def_value_str, ==, "default");
|
||||
i++;
|
||||
|
||||
g_assert_cmpstr(desc[i].name, ==, "str2");
|
||||
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_STRING);
|
||||
g_assert_cmpstr(desc[i].help, ==, NULL);
|
||||
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
|
||||
i++;
|
||||
}
|
||||
|
||||
g_assert_cmpstr(desc[i].name, ==, "str3");
|
||||
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_STRING);
|
||||
g_assert_cmpstr(desc[i].help, ==, NULL);
|
||||
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
|
||||
i++;
|
||||
|
||||
g_assert_cmpstr(desc[i].name, ==, "number1");
|
||||
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_NUMBER);
|
||||
g_assert_cmpstr(desc[i].help, ==,
|
||||
"Having help texts only for some options is okay");
|
||||
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
|
||||
i++;
|
||||
|
||||
g_assert_cmpstr(desc[i].name, ==, "number2");
|
||||
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_NUMBER);
|
||||
g_assert_cmpstr(desc[i].help, ==, NULL);
|
||||
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
|
||||
i++;
|
||||
|
||||
g_assert_cmpstr(desc[i].name, ==, NULL);
|
||||
}
|
||||
|
||||
static void append_verify_list_02(QemuOptDesc *desc)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
g_assert_cmpstr(desc[i].name, ==, "str1");
|
||||
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_STRING);
|
||||
g_assert_cmpstr(desc[i].help, ==, NULL);
|
||||
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
|
||||
i++;
|
||||
|
||||
g_assert_cmpstr(desc[i].name, ==, "str2");
|
||||
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_STRING);
|
||||
g_assert_cmpstr(desc[i].help, ==, NULL);
|
||||
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
|
||||
i++;
|
||||
|
||||
g_assert_cmpstr(desc[i].name, ==, "bool1");
|
||||
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_BOOL);
|
||||
g_assert_cmpstr(desc[i].help, ==, NULL);
|
||||
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
|
||||
i++;
|
||||
|
||||
g_assert_cmpstr(desc[i].name, ==, "bool2");
|
||||
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_BOOL);
|
||||
g_assert_cmpstr(desc[i].help, ==, NULL);
|
||||
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
|
||||
i++;
|
||||
|
||||
g_assert_cmpstr(desc[i].name, ==, "size1");
|
||||
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_SIZE);
|
||||
g_assert_cmpstr(desc[i].help, ==, NULL);
|
||||
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
|
||||
i++;
|
||||
|
||||
g_assert_cmpstr(desc[i].name, ==, "size2");
|
||||
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_SIZE);
|
||||
g_assert_cmpstr(desc[i].help, ==, NULL);
|
||||
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
|
||||
i++;
|
||||
|
||||
g_assert_cmpstr(desc[i].name, ==, "size3");
|
||||
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_SIZE);
|
||||
g_assert_cmpstr(desc[i].help, ==, NULL);
|
||||
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
|
||||
}
|
||||
|
||||
static void test_opts_append_to_null(void)
|
||||
{
|
||||
QemuOptsList *merged;
|
||||
|
||||
merged = qemu_opts_append(NULL, &opts_list_01);
|
||||
g_assert(merged != &opts_list_01);
|
||||
|
||||
g_assert_cmpstr(merged->name, ==, NULL);
|
||||
g_assert_cmpstr(merged->implied_opt_name, ==, NULL);
|
||||
g_assert_false(merged->merge_lists);
|
||||
|
||||
append_verify_list_01(merged->desc, true);
|
||||
|
||||
qemu_opts_free(merged);
|
||||
}
|
||||
|
||||
static void test_opts_append(void)
|
||||
{
|
||||
QemuOptsList *first, *merged;
|
||||
|
||||
first = qemu_opts_append(NULL, &opts_list_02);
|
||||
merged = qemu_opts_append(first, &opts_list_01);
|
||||
g_assert(first != &opts_list_02);
|
||||
g_assert(merged != &opts_list_01);
|
||||
|
||||
g_assert_cmpstr(merged->name, ==, NULL);
|
||||
g_assert_cmpstr(merged->implied_opt_name, ==, NULL);
|
||||
g_assert_false(merged->merge_lists);
|
||||
|
||||
append_verify_list_02(&merged->desc[0]);
|
||||
append_verify_list_01(&merged->desc[7], false);
|
||||
|
||||
qemu_opts_free(merged);
|
||||
}
|
||||
|
||||
static void test_opts_to_qdict_basic(void)
|
||||
{
|
||||
QemuOpts *opts;
|
||||
QDict *dict;
|
||||
|
||||
opts = qemu_opts_parse(&opts_list_01, "str1=foo,str2=,str3=bar,number1=42",
|
||||
false, &error_abort);
|
||||
g_assert(opts != NULL);
|
||||
|
||||
dict = qemu_opts_to_qdict(opts, NULL);
|
||||
g_assert(dict != NULL);
|
||||
|
||||
g_assert_cmpstr(qdict_get_str(dict, "str1"), ==, "foo");
|
||||
g_assert_cmpstr(qdict_get_str(dict, "str2"), ==, "");
|
||||
g_assert_cmpstr(qdict_get_str(dict, "str3"), ==, "bar");
|
||||
g_assert_cmpstr(qdict_get_str(dict, "number1"), ==, "42");
|
||||
g_assert_false(qdict_haskey(dict, "number2"));
|
||||
|
||||
QDECREF(dict);
|
||||
qemu_opts_del(opts);
|
||||
}
|
||||
|
||||
static void test_opts_to_qdict_filtered(void)
|
||||
{
|
||||
QemuOptsList *first, *merged;
|
||||
QemuOpts *opts;
|
||||
QDict *dict;
|
||||
|
||||
first = qemu_opts_append(NULL, &opts_list_02);
|
||||
merged = qemu_opts_append(first, &opts_list_01);
|
||||
|
||||
opts = qemu_opts_parse(merged,
|
||||
"str1=foo,str2=,str3=bar,bool1=off,number1=42",
|
||||
false, &error_abort);
|
||||
g_assert(opts != NULL);
|
||||
|
||||
/* Convert to QDict without deleting from opts */
|
||||
dict = qemu_opts_to_qdict_filtered(opts, NULL, &opts_list_01, false);
|
||||
g_assert(dict != NULL);
|
||||
g_assert_cmpstr(qdict_get_str(dict, "str1"), ==, "foo");
|
||||
g_assert_cmpstr(qdict_get_str(dict, "str2"), ==, "");
|
||||
g_assert_cmpstr(qdict_get_str(dict, "str3"), ==, "bar");
|
||||
g_assert_cmpstr(qdict_get_str(dict, "number1"), ==, "42");
|
||||
g_assert_false(qdict_haskey(dict, "number2"));
|
||||
g_assert_false(qdict_haskey(dict, "bool1"));
|
||||
QDECREF(dict);
|
||||
|
||||
dict = qemu_opts_to_qdict_filtered(opts, NULL, &opts_list_02, false);
|
||||
g_assert(dict != NULL);
|
||||
g_assert_cmpstr(qdict_get_str(dict, "str1"), ==, "foo");
|
||||
g_assert_cmpstr(qdict_get_str(dict, "str2"), ==, "");
|
||||
g_assert_cmpstr(qdict_get_str(dict, "bool1"), ==, "off");
|
||||
g_assert_false(qdict_haskey(dict, "str3"));
|
||||
g_assert_false(qdict_haskey(dict, "number1"));
|
||||
g_assert_false(qdict_haskey(dict, "number2"));
|
||||
QDECREF(dict);
|
||||
|
||||
/* Now delete converted options from opts */
|
||||
dict = qemu_opts_to_qdict_filtered(opts, NULL, &opts_list_01, true);
|
||||
g_assert(dict != NULL);
|
||||
g_assert_cmpstr(qdict_get_str(dict, "str1"), ==, "foo");
|
||||
g_assert_cmpstr(qdict_get_str(dict, "str2"), ==, "");
|
||||
g_assert_cmpstr(qdict_get_str(dict, "str3"), ==, "bar");
|
||||
g_assert_cmpstr(qdict_get_str(dict, "number1"), ==, "42");
|
||||
g_assert_false(qdict_haskey(dict, "number2"));
|
||||
g_assert_false(qdict_haskey(dict, "bool1"));
|
||||
QDECREF(dict);
|
||||
|
||||
dict = qemu_opts_to_qdict_filtered(opts, NULL, &opts_list_02, true);
|
||||
g_assert(dict != NULL);
|
||||
g_assert_cmpstr(qdict_get_str(dict, "bool1"), ==, "off");
|
||||
g_assert_false(qdict_haskey(dict, "str1"));
|
||||
g_assert_false(qdict_haskey(dict, "str2"));
|
||||
g_assert_false(qdict_haskey(dict, "str3"));
|
||||
g_assert_false(qdict_haskey(dict, "number1"));
|
||||
g_assert_false(qdict_haskey(dict, "number2"));
|
||||
QDECREF(dict);
|
||||
|
||||
g_assert_true(QTAILQ_EMPTY(&opts->head));
|
||||
|
||||
qemu_opts_del(opts);
|
||||
qemu_opts_free(merged);
|
||||
}
|
||||
|
||||
static void test_opts_to_qdict_duplicates(void)
|
||||
{
|
||||
QemuOpts *opts;
|
||||
QemuOpt *opt;
|
||||
QDict *dict;
|
||||
|
||||
opts = qemu_opts_parse(&opts_list_03, "foo=a,foo=b", false, &error_abort);
|
||||
g_assert(opts != NULL);
|
||||
|
||||
/* Verify that opts has two options with the same name */
|
||||
opt = QTAILQ_FIRST(&opts->head);
|
||||
g_assert_cmpstr(opt->name, ==, "foo");
|
||||
g_assert_cmpstr(opt->str , ==, "a");
|
||||
|
||||
opt = QTAILQ_NEXT(opt, next);
|
||||
g_assert_cmpstr(opt->name, ==, "foo");
|
||||
g_assert_cmpstr(opt->str , ==, "b");
|
||||
|
||||
opt = QTAILQ_NEXT(opt, next);
|
||||
g_assert(opt == NULL);
|
||||
|
||||
/* In the conversion to QDict, the last one wins */
|
||||
dict = qemu_opts_to_qdict(opts, NULL);
|
||||
g_assert(dict != NULL);
|
||||
g_assert_cmpstr(qdict_get_str(dict, "foo"), ==, "b");
|
||||
QDECREF(dict);
|
||||
|
||||
/* The last one still wins if entries are deleted, and both are deleted */
|
||||
dict = qemu_opts_to_qdict_filtered(opts, NULL, NULL, true);
|
||||
g_assert(dict != NULL);
|
||||
g_assert_cmpstr(qdict_get_str(dict, "foo"), ==, "b");
|
||||
QDECREF(dict);
|
||||
|
||||
g_assert_true(QTAILQ_EMPTY(&opts->head));
|
||||
|
||||
qemu_opts_del(opts);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
register_opts();
|
||||
|
@ -761,6 +1009,11 @@ int main(int argc, char *argv[])
|
|||
g_test_add_func("/qemu-opts/opts_parse/bool", test_opts_parse_bool);
|
||||
g_test_add_func("/qemu-opts/opts_parse/number", test_opts_parse_number);
|
||||
g_test_add_func("/qemu-opts/opts_parse/size", test_opts_parse_size);
|
||||
g_test_add_func("/qemu-opts/append_to_null", test_opts_append_to_null);
|
||||
g_test_add_func("/qemu-opts/append", test_opts_append);
|
||||
g_test_add_func("/qemu-opts/to_qdict/basic", test_opts_to_qdict_basic);
|
||||
g_test_add_func("/qemu-opts/to_qdict/filtered", test_opts_to_qdict_filtered);
|
||||
g_test_add_func("/qemu-opts/to_qdict/duplicates", test_opts_to_qdict_duplicates);
|
||||
g_test_run();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1007,14 +1007,23 @@ void qemu_opts_absorb_qdict(QemuOpts *opts, QDict *qdict, Error **errp)
|
|||
}
|
||||
|
||||
/*
|
||||
* Convert from QemuOpts to QDict.
|
||||
* The QDict values are of type QString.
|
||||
* Convert from QemuOpts to QDict. The QDict values are of type QString.
|
||||
*
|
||||
* If @list is given, only add those options to the QDict that are contained in
|
||||
* the list. If @del is true, any options added to the QDict are removed from
|
||||
* the QemuOpts, otherwise they remain there.
|
||||
*
|
||||
* If two options in @opts have the same name, they are processed in order
|
||||
* so that the last one wins (consistent with the reverse iteration in
|
||||
* qemu_opt_find()), but all of them are deleted if @del is true.
|
||||
*
|
||||
* TODO We'll want to use types appropriate for opt->desc->type, but
|
||||
* this is enough for now.
|
||||
*/
|
||||
QDict *qemu_opts_to_qdict(QemuOpts *opts, QDict *qdict)
|
||||
QDict *qemu_opts_to_qdict_filtered(QemuOpts *opts, QDict *qdict,
|
||||
QemuOptsList *list, bool del)
|
||||
{
|
||||
QemuOpt *opt;
|
||||
QemuOpt *opt, *next;
|
||||
|
||||
if (!qdict) {
|
||||
qdict = qdict_new();
|
||||
|
@ -1022,12 +1031,35 @@ QDict *qemu_opts_to_qdict(QemuOpts *opts, QDict *qdict)
|
|||
if (opts->id) {
|
||||
qdict_put_str(qdict, "id", opts->id);
|
||||
}
|
||||
QTAILQ_FOREACH(opt, &opts->head, next) {
|
||||
QTAILQ_FOREACH_SAFE(opt, &opts->head, next, next) {
|
||||
if (list) {
|
||||
QemuOptDesc *desc;
|
||||
bool found = false;
|
||||
for (desc = list->desc; desc->name; desc++) {
|
||||
if (!strcmp(desc->name, opt->name)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
qdict_put_str(qdict, opt->name, opt->str);
|
||||
if (del) {
|
||||
qemu_opt_del(opt);
|
||||
}
|
||||
}
|
||||
return qdict;
|
||||
}
|
||||
|
||||
/* Copy all options in a QemuOpts to the given QDict. See
|
||||
* qemu_opts_to_qdict_filtered() for details. */
|
||||
QDict *qemu_opts_to_qdict(QemuOpts *opts, QDict *qdict)
|
||||
{
|
||||
return qemu_opts_to_qdict_filtered(opts, qdict, NULL, false);
|
||||
}
|
||||
|
||||
/* Validate parsed opts against descriptions where no
|
||||
* descriptions were provided in the QemuOptsList.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue