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:
Peter Maydell 2018-03-12 10:08:09 +00:00
commit 12c06d6f96
60 changed files with 3597 additions and 987 deletions

138
block.c
View file

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

View file

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

76
block/create.c Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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, &copy);
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,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
.......................
........................
----------------------------------------------------------------------
Ran 23 tests
Ran 24 tests
OK

View file

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

View file

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

View file

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

View file

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

0
tests/qemu-iotests/096 Normal file → Executable file
View file

View file

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

0
tests/qemu-iotests/124 Normal file → Executable file
View file

0
tests/qemu-iotests/129 Normal file → Executable file
View file

0
tests/qemu-iotests/132 Normal file → Executable file
View file

0
tests/qemu-iotests/136 Normal file → Executable file
View file

0
tests/qemu-iotests/139 Normal file → Executable file
View file

0
tests/qemu-iotests/148 Normal file → Executable file
View file

0
tests/qemu-iotests/152 Normal file → Executable file
View file

View file

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

View file

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

0
tests/qemu-iotests/163 Normal file → Executable file
View file

View file

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

View file

@ -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
tests/qemu-iotests/205 Normal file → Executable file
View file

436
tests/qemu-iotests/206 Executable file
View file

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

209
tests/qemu-iotests/206.out Normal file
View file

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

261
tests/qemu-iotests/207 Executable file
View file

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

View file

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

View file

@ -202,3 +202,5 @@
203 rw auto
204 rw auto quick
205 rw auto quick
206 rw auto
207 rw auto

View file

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

View file

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