diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2018-03-12 10:08:09 +0000 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2018-03-12 10:08:09 +0000 |
commit | 12c06d6f967a63515399b9e1f6a40f5ce871a8b7 (patch) | |
tree | d5c2f32efcb3b9622c042c94250d0fc3fdb56167 /block | |
parent | 20f59d120053a26521fa0177ea07f947bc8bfff0 (diff) | |
parent | a1be5921e35dcf84ce9e3c9a5c3029cea530a60b (diff) |
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>
Diffstat (limited to 'block')
-rw-r--r-- | block/Makefile.objs | 2 | ||||
-rw-r--r-- | block/create.c | 76 | ||||
-rw-r--r-- | block/crypto.c | 7 | ||||
-rw-r--r-- | block/file-posix.c | 93 | ||||
-rw-r--r-- | block/file-win32.c | 47 | ||||
-rw-r--r-- | block/gluster.c | 135 | ||||
-rw-r--r-- | block/iscsi.c | 6 | ||||
-rw-r--r-- | block/nfs.c | 244 | ||||
-rw-r--r-- | block/parallels.c | 17 | ||||
-rw-r--r-- | block/qcow2-bitmap.c | 4 | ||||
-rw-r--r-- | block/qcow2-cluster.c | 24 | ||||
-rw-r--r-- | block/qcow2-refcount.c | 52 | ||||
-rw-r--r-- | block/qcow2-snapshot.c | 24 | ||||
-rw-r--r-- | block/qcow2.c | 552 | ||||
-rw-r--r-- | block/qcow2.h | 12 | ||||
-rw-r--r-- | block/qed-check.c | 1 | ||||
-rw-r--r-- | block/qed-table.c | 26 | ||||
-rw-r--r-- | block/qed.c | 66 | ||||
-rw-r--r-- | block/rbd.c | 403 | ||||
-rw-r--r-- | block/sheepdog.c | 321 | ||||
-rw-r--r-- | block/ssh.c | 290 | ||||
-rw-r--r-- | block/vdi.c | 6 | ||||
-rw-r--r-- | block/vhdx.c | 7 | ||||
-rw-r--r-- | block/vmdk.c | 7 |
24 files changed, 1517 insertions, 905 deletions
diff --git a/block/Makefile.objs b/block/Makefile.objs index aede94f105..d644bac60a 100644 --- a/block/Makefile.objs +++ b/block/Makefile.objs @@ -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 diff --git a/block/create.c b/block/create.c new file mode 100644 index 0000000000..8bd8a03719 --- /dev/null +++ b/block/create.c @@ -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); + } +} diff --git a/block/crypto.c b/block/crypto.c index 17b5c0abad..e6095e7807 100644 --- a/block/crypto.c +++ b/block/crypto.c @@ -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, diff --git a/block/file-posix.c b/block/file-posix.c index 7f2cc63c60..d7fb772c14 100644 --- a/block/file-posix.c +++ b/block/file-posix.c @@ -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, diff --git a/block/file-win32.c b/block/file-win32.c index 4a430d45f1..2e2f746bb1 100644 --- a/block/file-win32.c +++ b/block/file-win32.c @@ -553,30 +553,59 @@ static int64_t raw_get_allocated_file_size(BlockDriverState *bs) return st.st_size; } -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; - int64_t total_size = 0; - strstart(filename, "file:", &filename); + 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); + 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(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + 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, total_size); + 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) +{ + BlockdevCreateOptions options; + int64_t total_size = 0; + + strstart(filename, "file:", &filename); + + /* Read out options */ + total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0), + BDRV_SECTOR_SIZE); + + 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", diff --git a/block/gluster.c b/block/gluster.c index 79b4cfdf74..63d3c37d4c 100644 --- a/block/gluster.c +++ b/block/gluster.c @@ -655,9 +655,11 @@ out: return -errno; } -static struct glfs *qemu_gluster_init(BlockdevOptionsGluster *gconf, - const char *filename, - QDict *options, Error **errp) +/* Converts options given in @filename and the @options QDict into the QAPI + * object @gconf. */ +static int qemu_gluster_parse(BlockdevOptionsGluster *gconf, + const char *filename, + QDict *options, Error **errp) { int ret; if (filename) { @@ -668,8 +670,7 @@ static struct glfs *qemu_gluster_init(BlockdevOptionsGluster *gconf, "[host[:port]]volume/path[?socket=...]" "[,file.debug=N]" "[,file.logfile=/path/filename.log]\n"); - errno = -ret; - return NULL; + return ret; } } else { ret = qemu_gluster_parse_json(gconf, options, errp); @@ -685,10 +686,23 @@ static struct glfs *qemu_gluster_init(BlockdevOptionsGluster *gconf, "file.server.1.transport=unix," "file.server.1.socket=/var/run/glusterd.socket ..." "\n"); - errno = -ret; - return NULL; + return ret; } + } + return 0; +} + +static struct glfs *qemu_gluster_init(BlockdevOptionsGluster *gconf, + const char *filename, + QDict *options, Error **errp) +{ + int ret; + + ret = qemu_gluster_parse(gconf, filename, options, errp); + if (ret < 0) { + errno = -ret; + return NULL; } return qemu_gluster_glfs_init(gconf, errp); @@ -1021,20 +1035,72 @@ static int qemu_gluster_do_truncate(struct glfs_fd *fd, int64_t offset, return 0; } +static int qemu_gluster_co_create(BlockdevCreateOptions *options, + Error **errp) +{ + BlockdevCreateOptionsGluster *opts = &options->u.gluster; + struct glfs *glfs; + struct glfs_fd *fd = NULL; + int ret = 0; + + assert(options->driver == BLOCKDEV_DRIVER_GLUSTER); + + glfs = qemu_gluster_glfs_init(opts->location, errp); + if (!glfs) { + ret = -errno; + goto out; + } + + fd = glfs_creat(glfs, opts->location->path, + O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR); + if (!fd) { + ret = -errno; + goto out; + } + + ret = qemu_gluster_do_truncate(fd, opts->size, opts->preallocation, errp); + +out: + if (fd) { + if (glfs_close(fd) != 0 && ret == 0) { + ret = -errno; + } + } + glfs_clear_preopened(glfs); + return ret; +} + static int coroutine_fn qemu_gluster_co_create_opts(const char *filename, QemuOpts *opts, Error **errp) { + BlockdevCreateOptions *options; + BlockdevCreateOptionsGluster *gopts; BlockdevOptionsGluster *gconf; - struct glfs *glfs; - struct glfs_fd *fd = NULL; - int ret = 0; - PreallocMode prealloc; - int64_t total_size = 0; char *tmp = NULL; Error *local_err = NULL; + int ret; + + options = g_new0(BlockdevCreateOptions, 1); + options->driver = BLOCKDEV_DRIVER_GLUSTER; + gopts = &options->u.gluster; gconf = g_new0(BlockdevOptionsGluster, 1); + gopts->location = gconf; + + gopts->size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0), + BDRV_SECTOR_SIZE); + + tmp = qemu_opt_get_del(opts, BLOCK_OPT_PREALLOC); + gopts->preallocation = qapi_enum_parse(&PreallocMode_lookup, tmp, + PREALLOC_MODE_OFF, &local_err); + g_free(tmp); + if (local_err) { + error_propagate(errp, local_err); + ret = -EINVAL; + goto fail; + } + gconf->debug = qemu_opt_get_number_del(opts, GLUSTER_OPT_DEBUG, GLUSTER_DEBUG_DEFAULT); if (gconf->debug < 0) { @@ -1050,42 +1116,19 @@ static int coroutine_fn qemu_gluster_co_create_opts(const char *filename, } gconf->has_logfile = true; - glfs = qemu_gluster_init(gconf, filename, NULL, errp); - if (!glfs) { - ret = -errno; - goto out; - } - - 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_parse(gconf, filename, NULL, 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_co_create(options, errp); + if (ret < 0) { + goto fail; } - 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, diff --git a/block/iscsi.c b/block/iscsi.c index 8bf0e87244..a82170f16e 100644 --- a/block/iscsi.c +++ b/block/iscsi.c @@ -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, diff --git a/block/nfs.c b/block/nfs.c index 1d82ff5042..2577df4b26 100644 --- a/block/nfs.c +++ b/block/nfs.c @@ -367,49 +367,6 @@ static int coroutine_fn nfs_co_flush(BlockDriverState *bs) return task.ret; } -static QemuOptsList runtime_opts = { - .name = "nfs", - .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head), - .desc = { - { - .name = "path", - .type = QEMU_OPT_STRING, - .help = "Path of the image on the host", - }, - { - .name = "user", - .type = QEMU_OPT_NUMBER, - .help = "UID value to use when talking to the server", - }, - { - .name = "group", - .type = QEMU_OPT_NUMBER, - .help = "GID value to use when talking to the server", - }, - { - .name = "tcp-syn-count", - .type = QEMU_OPT_NUMBER, - .help = "Number of SYNs to send during the session establish", - }, - { - .name = "readahead-size", - .type = QEMU_OPT_NUMBER, - .help = "Set the readahead size in bytes", - }, - { - .name = "page-cache-size", - .type = QEMU_OPT_NUMBER, - .help = "Set the pagecache size in bytes", - }, - { - .name = "debug", - .type = QEMU_OPT_NUMBER, - .help = "Set the NFS debug level (max 2)", - }, - { /* end of list */ } - }, -}; - static void nfs_detach_aio_context(BlockDriverState *bs) { NFSClient *client = bs->opaque; @@ -452,71 +409,16 @@ static void nfs_file_close(BlockDriverState *bs) nfs_client_close(client); } -static NFSServer *nfs_config(QDict *options, Error **errp) -{ - NFSServer *server = NULL; - QDict *addr = NULL; - QObject *crumpled_addr = NULL; - Visitor *iv = NULL; - Error *local_error = NULL; - - qdict_extract_subqdict(options, &addr, "server."); - if (!qdict_size(addr)) { - error_setg(errp, "NFS server address missing"); - goto out; - } - - crumpled_addr = qdict_crumple(addr, errp); - if (!crumpled_addr) { - goto out; - } - - /* - * Caution: this works only because all scalar members of - * NFSServer are QString in @crumpled_addr. The visitor expects - * @crumpled_addr to be typed according to the QAPI schema. It - * is when @options come from -blockdev or blockdev_add. But when - * they come from -drive, they're all QString. - */ - iv = qobject_input_visitor_new(crumpled_addr); - visit_type_NFSServer(iv, NULL, &server, &local_error); - if (local_error) { - error_propagate(errp, local_error); - goto out; - } - -out: - QDECREF(addr); - qobject_decref(crumpled_addr); - visit_free(iv); - return server; -} - - -static int64_t nfs_client_open(NFSClient *client, QDict *options, +static int64_t nfs_client_open(NFSClient *client, BlockdevOptionsNfs *opts, int flags, int open_flags, Error **errp) { int64_t ret = -EINVAL; - QemuOpts *opts = NULL; - Error *local_err = NULL; struct stat st; char *file = NULL, *strp = NULL; qemu_mutex_init(&client->mutex); - opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort); - qemu_opts_absorb_qdict(opts, options, &local_err); - if (local_err) { - error_propagate(errp, local_err); - ret = -EINVAL; - goto fail; - } - client->path = g_strdup(qemu_opt_get(opts, "path")); - if (!client->path) { - ret = -EINVAL; - error_setg(errp, "No path was specified"); - goto fail; - } + client->path = g_strdup(opts->path); strp = strrchr(client->path, '/'); if (strp == NULL) { @@ -526,12 +428,10 @@ static int64_t nfs_client_open(NFSClient *client, QDict *options, file = g_strdup(strp); *strp = 0; - /* Pop the config into our state object, Exit if invalid */ - client->server = nfs_config(options, errp); - if (!client->server) { - ret = -EINVAL; - goto fail; - } + /* Steal the NFSServer object from opts; set the original pointer to NULL + * to avoid use after free and double free. */ + client->server = opts->server; + opts->server = NULL; client->context = nfs_init_context(); if (client->context == NULL) { @@ -539,29 +439,29 @@ static int64_t nfs_client_open(NFSClient *client, QDict *options, goto fail; } - if (qemu_opt_get(opts, "user")) { - client->uid = qemu_opt_get_number(opts, "user", 0); + if (opts->has_user) { + client->uid = opts->user; nfs_set_uid(client->context, client->uid); } - if (qemu_opt_get(opts, "group")) { - client->gid = qemu_opt_get_number(opts, "group", 0); + if (opts->has_group) { + client->gid = opts->group; nfs_set_gid(client->context, client->gid); } - if (qemu_opt_get(opts, "tcp-syn-count")) { - client->tcp_syncnt = qemu_opt_get_number(opts, "tcp-syn-count", 0); + if (opts->has_tcp_syn_count) { + client->tcp_syncnt = opts->tcp_syn_count; nfs_set_tcp_syncnt(client->context, client->tcp_syncnt); } #ifdef LIBNFS_FEATURE_READAHEAD - if (qemu_opt_get(opts, "readahead-size")) { + if (opts->has_readahead_size) { if (open_flags & BDRV_O_NOCACHE) { error_setg(errp, "Cannot enable NFS readahead " "if cache.direct = on"); goto fail; } - client->readahead = qemu_opt_get_number(opts, "readahead-size", 0); + client->readahead = opts->readahead_size; if (client->readahead > QEMU_NFS_MAX_READAHEAD_SIZE) { warn_report("Truncating NFS readahead size to %d", QEMU_NFS_MAX_READAHEAD_SIZE); @@ -576,13 +476,13 @@ static int64_t nfs_client_open(NFSClient *client, QDict *options, #endif #ifdef LIBNFS_FEATURE_PAGECACHE - if (qemu_opt_get(opts, "page-cache-size")) { + if (opts->has_page_cache_size) { if (open_flags & BDRV_O_NOCACHE) { error_setg(errp, "Cannot enable NFS pagecache " "if cache.direct = on"); goto fail; } - client->pagecache = qemu_opt_get_number(opts, "page-cache-size", 0); + client->pagecache = opts->page_cache_size; if (client->pagecache > QEMU_NFS_MAX_PAGECACHE_SIZE) { warn_report("Truncating NFS pagecache size to %d pages", QEMU_NFS_MAX_PAGECACHE_SIZE); @@ -595,8 +495,8 @@ static int64_t nfs_client_open(NFSClient *client, QDict *options, #endif #ifdef LIBNFS_FEATURE_DEBUG - if (qemu_opt_get(opts, "debug")) { - client->debug = qemu_opt_get_number(opts, "debug", 0); + if (opts->has_debug) { + client->debug = opts->debug; /* limit the maximum debug level to avoid potential flooding * of our log files. */ if (client->debug > QEMU_NFS_MAX_DEBUG_LEVEL) { @@ -647,11 +547,53 @@ static int64_t nfs_client_open(NFSClient *client, QDict *options, fail: nfs_client_close(client); out: - qemu_opts_del(opts); g_free(file); return ret; } +static BlockdevOptionsNfs *nfs_options_qdict_to_qapi(QDict *options, + Error **errp) +{ + BlockdevOptionsNfs *opts = NULL; + QObject *crumpled = NULL; + Visitor *v; + Error *local_err = NULL; + + crumpled = qdict_crumple(options, errp); + if (crumpled == NULL) { + return NULL; + } + + v = qobject_input_visitor_new_keyval(crumpled); + visit_type_BlockdevOptionsNfs(v, NULL, &opts, &local_err); + visit_free(v); + qobject_decref(crumpled); + + if (local_err) { + return NULL; + } + + return opts; +} + +static int64_t nfs_client_open_qdict(NFSClient *client, QDict *options, + int flags, int open_flags, Error **errp) +{ + BlockdevOptionsNfs *opts; + int ret; + + opts = nfs_options_qdict_to_qapi(options, errp); + if (opts == NULL) { + ret = -EINVAL; + goto fail; + } + + ret = nfs_client_open(client, opts, flags, open_flags, errp); +fail: + qapi_free_BlockdevOptionsNfs(opts); + return ret; +} + static int nfs_file_open(BlockDriverState *bs, QDict *options, int flags, Error **errp) { NFSClient *client = bs->opaque; @@ -659,9 +601,9 @@ static int nfs_file_open(BlockDriverState *bs, QDict *options, int flags, client->aio_context = bdrv_get_aio_context(bs); - ret = nfs_client_open(client, options, - (flags & BDRV_O_RDWR) ? O_RDWR : O_RDONLY, - bs->open_flags, errp); + ret = nfs_client_open_qdict(client, options, + (flags & BDRV_O_RDWR) ? O_RDWR : O_RDONLY, + bs->open_flags, errp); if (ret < 0) { return ret; } @@ -684,18 +626,43 @@ static QemuOptsList nfs_create_opts = { } }; -static int coroutine_fn nfs_file_co_create_opts(const char *url, QemuOpts *opts, - Error **errp) +static int nfs_file_co_create(BlockdevCreateOptions *options, Error **errp) { - int64_t ret, total_size; + BlockdevCreateOptionsNfs *opts = &options->u.nfs; NFSClient *client = g_new0(NFSClient, 1); - QDict *options = NULL; + int ret; + + assert(options->driver == BLOCKDEV_DRIVER_NFS); client->aio_context = qemu_get_aio_context(); + ret = nfs_client_open(client, opts->location, O_CREAT, 0, errp); + if (ret < 0) { + goto out; + } + ret = nfs_ftruncate(client->context, client->fh, opts->size); + nfs_client_close(client); + +out: + g_free(client); + return ret; +} + +static int coroutine_fn nfs_file_co_create_opts(const char *url, QemuOpts *opts, + Error **errp) +{ + BlockdevCreateOptions *create_options; + BlockdevCreateOptionsNfs *nfs_opts; + QDict *options; + int ret; + + create_options = g_new0(BlockdevCreateOptions, 1); + create_options->driver = BLOCKDEV_DRIVER_NFS; + nfs_opts = &create_options->u.nfs; + /* Read out options */ - total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0), - BDRV_SECTOR_SIZE); + nfs_opts->size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0), + BDRV_SECTOR_SIZE); options = qdict_new(); ret = nfs_parse_uri(url, options, errp); @@ -703,15 +670,21 @@ static int coroutine_fn nfs_file_co_create_opts(const char *url, QemuOpts *opts, goto out; } - ret = nfs_client_open(client, options, O_CREAT, 0, errp); + nfs_opts->location = nfs_options_qdict_to_qapi(options, errp); + if (nfs_opts->location == NULL) { + ret = -EINVAL; + goto out; + } + + ret = nfs_file_co_create(create_options, errp); if (ret < 0) { goto out; } - ret = nfs_ftruncate(client->context, client->fh, total_size); - nfs_client_close(client); + + ret = 0; out: QDECREF(options); - g_free(client); + qapi_free_BlockdevCreateOptions(create_options); return ret; } @@ -876,8 +849,8 @@ static void nfs_refresh_filename(BlockDriverState *bs, QDict *options) } #ifdef LIBNFS_FEATURE_PAGECACHE -static void nfs_invalidate_cache(BlockDriverState *bs, - Error **errp) +static void coroutine_fn nfs_co_invalidate_cache(BlockDriverState *bs, + Error **errp) { NFSClient *client = bs->opaque; nfs_pagecache_invalidate(client->context, client->fh); @@ -898,6 +871,7 @@ static BlockDriver bdrv_nfs = { .bdrv_file_open = nfs_file_open, .bdrv_close = nfs_file_close, + .bdrv_co_create = nfs_file_co_create, .bdrv_co_create_opts = nfs_file_co_create_opts, .bdrv_reopen_prepare = nfs_reopen_prepare, @@ -910,7 +884,7 @@ static BlockDriver bdrv_nfs = { .bdrv_refresh_filename = nfs_refresh_filename, #ifdef LIBNFS_FEATURE_PAGECACHE - .bdrv_invalidate_cache = nfs_invalidate_cache, + .bdrv_co_invalidate_cache = nfs_co_invalidate_cache, #endif }; diff --git a/block/parallels.c b/block/parallels.c index 81085795c2..c13cb619e6 100644 --- a/block/parallels.c +++ b/block/parallels.c @@ -378,8 +378,9 @@ static coroutine_fn int parallels_co_readv(BlockDriverState *bs, } -static int parallels_check(BlockDriverState *bs, BdrvCheckResult *res, - BdrvCheckMode fix) +static int coroutine_fn parallels_co_check(BlockDriverState *bs, + BdrvCheckResult *res, + BdrvCheckMode fix) { BDRVParallelsState *s = bs->opaque; int64_t size, prev_off, high_off; @@ -394,6 +395,7 @@ static int parallels_check(BlockDriverState *bs, BdrvCheckResult *res, return size; } + qemu_co_mutex_lock(&s->lock); if (s->header_unclean) { fprintf(stderr, "%s image was not closed correctly\n", fix & BDRV_FIX_ERRORS ? "Repairing" : "ERROR"); @@ -442,11 +444,12 @@ static int parallels_check(BlockDriverState *bs, BdrvCheckResult *res, prev_off = off; } + ret = 0; if (flush_bat) { ret = bdrv_pwrite_sync(bs->file, 0, s->header, s->header_size); if (ret < 0) { res->check_errors++; - return ret; + goto out; } } @@ -465,13 +468,15 @@ static int parallels_check(BlockDriverState *bs, BdrvCheckResult *res, if (ret < 0) { error_report_err(local_err); res->check_errors++; - return ret; + goto out; } res->leaks_fixed += count; } } - return 0; +out: + qemu_co_mutex_unlock(&s->lock); + return ret; } @@ -799,7 +804,7 @@ static BlockDriver bdrv_parallels = { .bdrv_co_writev = parallels_co_writev, .supports_backing = true, .bdrv_co_create_opts = parallels_co_create_opts, - .bdrv_check = parallels_check, + .bdrv_co_check = parallels_co_check, .create_opts = ¶llels_create_opts, }; diff --git a/block/qcow2-bitmap.c b/block/qcow2-bitmap.c index 5127276f90..3010adb909 100644 --- a/block/qcow2-bitmap.c +++ b/block/qcow2-bitmap.c @@ -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; } diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c index 98908c4264..1aee726c6a 100644 --- a/block/qcow2-cluster.c +++ b/block/qcow2-cluster.c @@ -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; + + 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; + } - uint64_t *new_l1_table = - g_try_realloc(l1_table, l1_sectors * BDRV_SECTOR_SIZE); + 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; } diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c index 126cca3276..362deaf303 100644 --- a/block/qcow2-refcount.c +++ b/block/qcow2-refcount.c @@ -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; } diff --git a/block/qcow2-snapshot.c b/block/qcow2-snapshot.c index cee25f582b..74293be470 100644 --- a/block/qcow2-snapshot.c +++ b/block/qcow2-snapshot.c @@ -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, diff --git a/block/qcow2.c b/block/qcow2.c index 071dc4d608..7472af6931 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -37,7 +37,8 @@ #include "qemu/option_int.h" #include "qemu/cutils.h" #include "qemu/bswap.h" -#include "qapi/opts-visitor.h" +#include "qapi/qobject-input-visitor.h" +#include "qapi/qapi-visit-block-core.h" #include "block/crypto.h" /* @@ -500,7 +501,7 @@ static int qcow2_mark_clean(BlockDriverState *bs) s->incompatible_features &= ~QCOW2_INCOMPAT_DIRTY; - ret = bdrv_flush(bs); + ret = qcow2_flush_caches(bs); if (ret < 0) { return ret; } @@ -530,7 +531,7 @@ int qcow2_mark_consistent(BlockDriverState *bs) BDRVQcow2State *s = bs->opaque; if (s->incompatible_features & QCOW2_INCOMPAT_CORRUPT) { - int ret = bdrv_flush(bs); + int ret = qcow2_flush_caches(bs); if (ret < 0) { return ret; } @@ -541,8 +542,9 @@ int qcow2_mark_consistent(BlockDriverState *bs) return 0; } -static int qcow2_check(BlockDriverState *bs, BdrvCheckResult *result, - BdrvCheckMode fix) +static int coroutine_fn qcow2_co_check_locked(BlockDriverState *bs, + BdrvCheckResult *result, + BdrvCheckMode fix) { int ret = qcow2_check_refcounts(bs, result, fix); if (ret < 0) { @@ -559,26 +561,36 @@ static int qcow2_check(BlockDriverState *bs, BdrvCheckResult *result, return ret; } -static int validate_table_offset(BlockDriverState *bs, uint64_t offset, - uint64_t entries, size_t entry_len) +static int coroutine_fn qcow2_co_check(BlockDriverState *bs, + BdrvCheckResult *result, + BdrvCheckMode fix) { BDRVQcow2State *s = bs->opaque; - uint64_t size; + int ret; - /* 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; - } + qemu_co_mutex_lock(&s->lock); + ret = qcow2_co_check_locked(bs, result, fix); + qemu_co_mutex_unlock(&s->lock); + return ret; +} - size = entries * entry_len; +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 (INT64_MAX - size < offset) { - return -EINVAL; + if (entries > max_size_bytes / entry_len) { + error_setg(errp, "%s too large", table_name); + return -EFBIG; } - /* Tables must be cluster aligned */ - if (offset_into_cluster(s, offset) != 0) { + /* 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 ((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; - - options = qemu_opts_to_qdict(opts, NULL); - qdict_extract_subqdict(options, &encryptopts, "encrypt."); - QDECREF(options); + int fmt, ret; - 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; + + bs = bdrv_open_blockdev_ref(qcow2_opts->file, errp); + if (bs == NULL) { + return -EIO; } - ret = bdrv_create_file(filename, opts, &local_err); - if (ret < 0) { - error_propagate(errp, local_err); - return ret; + /* 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; } - 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); - return -EIO; + 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"); - } - cluster_size = qcow2_opt_get_cluster_size_del(opts, &local_err); - if (local_err) { - error_propagate(errp, local_err); + /* 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); + } + + 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); - if (local_err) { - error_propagate(errp, local_err); - ret = -EINVAL; + + /* Create and open the file (protocol layer) */ + ret = bdrv_create_file(filename, opts, errp); + if (ret < 0) { goto finish; } - version = qcow2_opt_get_version_del(opts, &local_err); - if (local_err) { - error_propagate(errp, local_err); - ret = -EINVAL; + bs = bdrv_open(filename, NULL, NULL, + BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, errp); + if (bs == NULL) { + ret = -EIO; goto finish; } - if (qemu_opt_get_bool_del(opts, BLOCK_OPT_LAZY_REFCOUNTS, false)) { - flags |= BLOCK_FLAG_LAZY_REFCOUNTS; - } + /* Set 'driver' and 'node' options */ + qdict_put_str(qdict, "driver", "qcow2"); + qdict_put_str(qdict, "file", bs->node_name); - if (backing_file && prealloc != PREALLOC_MODE_OFF) { - error_setg(errp, "Backing file and preallocation cannot be used at " - "the same time"); + /* 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; } - 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; - } + v = qobject_input_visitor_new_keyval(QOBJECT(qdict)); + visit_type_BlockdevCreateOptions(v, NULL, &create_options, &local_err); + visit_free(v); - 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); + /* Silently round up size */ + create_options->u.qcow2.size = ROUND_UP(create_options->u.qcow2.size, + BDRV_SECTOR_SIZE); - 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); + /* Create the qcow2 image (format layer) */ + ret = qcow2_co_create(create_options, errp); + if (ret < 0) { + goto finish; + } + 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, diff --git a/block/qcow2.h b/block/qcow2.h index 1a84cc77b0..ccb92a9696 100644 --- a/block/qcow2.h +++ b/block/qcow2.h @@ -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); diff --git a/block/qed-check.c b/block/qed-check.c index dcd4f036b8..0edac03159 100644 --- a/block/qed-check.c +++ b/block/qed-check.c @@ -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 = { diff --git a/block/qed-table.c b/block/qed-table.c index eead8b0fc7..7df5680adb 100644 --- a/block/qed-table.c +++ b/block/qed-table.c @@ -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) { diff --git a/block/qed.c b/block/qed.c index 72cf2f58ab..5e6a6bfaa0 100644 --- a/block/qed.c +++ b/block/qed.c @@ -381,8 +381,9 @@ static void bdrv_qed_init_state(BlockDriverState *bs) qemu_co_queue_init(&s->allocating_write_reqs); } -static int bdrv_qed_do_open(BlockDriverState *bs, QDict *options, int flags, - Error **errp) +/* Called with table_lock held. */ +static int coroutine_fn bdrv_qed_do_open(BlockDriverState *bs, QDict *options, + int flags, Error **errp) { BDRVQEDState *s = bs->opaque; QEDHeader le_header; @@ -513,9 +514,35 @@ out: return ret; } +typedef struct QEDOpenCo { + BlockDriverState *bs; + QDict *options; + int flags; + Error **errp; + int ret; +} QEDOpenCo; + +static void coroutine_fn bdrv_qed_open_entry(void *opaque) +{ + QEDOpenCo *qoc = opaque; + BDRVQEDState *s = qoc->bs->opaque; + + qemu_co_mutex_lock(&s->table_lock); + qoc->ret = bdrv_qed_do_open(qoc->bs, qoc->options, qoc->flags, qoc->errp); + qemu_co_mutex_unlock(&s->table_lock); +} + static int bdrv_qed_open(BlockDriverState *bs, QDict *options, int flags, Error **errp) { + QEDOpenCo qoc = { + .bs = bs, + .options = options, + .flags = flags, + .errp = errp, + .ret = -EINPROGRESS + }; + bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file, false, errp); if (!bs->file) { @@ -523,7 +550,14 @@ static int bdrv_qed_open(BlockDriverState *bs, QDict *options, int flags, } bdrv_qed_init_state(bs); - return bdrv_qed_do_open(bs, options, flags, errp); + if (qemu_in_coroutine()) { + bdrv_qed_open_entry(&qoc); + } else { + qemu_coroutine_enter(qemu_coroutine_create(bdrv_qed_open_entry, &qoc)); + BDRV_POLL_WHILE(bs, qoc.ret == -EINPROGRESS); + } + BDRV_POLL_WHILE(bs, qoc.ret == -EINPROGRESS); + return qoc.ret; } static void bdrv_qed_refresh_limits(BlockDriverState *bs, Error **errp) @@ -1487,7 +1521,8 @@ static int bdrv_qed_change_backing_file(BlockDriverState *bs, return ret; } -static void bdrv_qed_invalidate_cache(BlockDriverState *bs, Error **errp) +static void coroutine_fn bdrv_qed_co_invalidate_cache(BlockDriverState *bs, + Error **errp) { BDRVQEDState *s = bs->opaque; Error *local_err = NULL; @@ -1496,13 +1531,9 @@ static void bdrv_qed_invalidate_cache(BlockDriverState *bs, Error **errp) bdrv_qed_close(bs); bdrv_qed_init_state(bs); - if (qemu_in_coroutine()) { - qemu_co_mutex_lock(&s->table_lock); - } + qemu_co_mutex_lock(&s->table_lock); ret = bdrv_qed_do_open(bs, NULL, bs->open_flags, &local_err); - if (qemu_in_coroutine()) { - qemu_co_mutex_unlock(&s->table_lock); - } + qemu_co_mutex_unlock(&s->table_lock); if (local_err) { error_propagate(errp, local_err); error_prepend(errp, "Could not reopen qed layer: "); @@ -1513,12 +1544,17 @@ static void bdrv_qed_invalidate_cache(BlockDriverState *bs, Error **errp) } } -static int bdrv_qed_check(BlockDriverState *bs, BdrvCheckResult *result, - BdrvCheckMode fix) +static int bdrv_qed_co_check(BlockDriverState *bs, BdrvCheckResult *result, + BdrvCheckMode fix) { BDRVQEDState *s = bs->opaque; + int ret; + + qemu_co_mutex_lock(&s->table_lock); + ret = qed_check(s, result, !!fix); + qemu_co_mutex_unlock(&s->table_lock); - return qed_check(s, result, !!fix); + 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, diff --git a/block/rbd.c b/block/rbd.c index c7dd32e213..294ed07ac4 100644 --- a/block/rbd.c +++ b/block/rbd.c @@ -24,6 +24,8 @@ #include "qapi/qmp/qdict.h" #include "qapi/qmp/qjson.h" #include "qapi/qmp/qlist.h" +#include "qapi/qobject-input-visitor.h" +#include "qapi/qapi-visit-block-core.h" /* * When specifying the image filename use: @@ -101,6 +103,11 @@ typedef struct BDRVRBDState { char *snap; } BDRVRBDState; +static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx, + BlockdevOptionsRbd *opts, bool cache, + const char *keypairs, const char *secretid, + Error **errp); + static char *qemu_rbd_next_tok(char *src, char delim, char **p) { char *end; @@ -268,13 +275,14 @@ static int qemu_rbd_set_keypairs(rados_t cluster, const char *keypairs_json, key = qstring_get_str(name); ret = rados_conf_set(cluster, key, qstring_get_str(value)); - QDECREF(name); QDECREF(value); if (ret < 0) { error_setg_errno(errp, -ret, "invalid conf option %s", key); + QDECREF(name); ret = -EINVAL; break; } + QDECREF(name); } QDECREF(keypairs); @@ -325,67 +333,93 @@ 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 */ } }, }; -static int coroutine_fn qemu_rbd_co_create_opts(const char *filename, - QemuOpts *opts, - Error **errp) +/* 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) { - 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; + BlockdevCreateOptionsRbd *opts = &options->u.rbd; rados_t cluster; rados_ioctx_t io_ctx; - QDict *options = NULL; - int ret = 0; + int obj_order = 0; + int ret; - secretid = qemu_opt_get(opts, "password-secret"); + assert(options->driver == BLOCKDEV_DRIVER_RBD); + if (opts->location->has_snapshot) { + error_setg(errp, "Can't use snapshot name for image creation"); + return -EINVAL; + } - /* 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 (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"); - ret = -EINVAL; - goto exit; + return -EINVAL; } if (objsize < 4096) { error_setg(errp, "obj size too small"); - ret = -EINVAL; - goto exit; + 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; + const char *keypairs, *password_secret; + QDict *options = NULL; + int ret = 0; + + 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 */ + 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); if (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"); - - ret = rados_create(&cluster, user); + 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 = 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; - - 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; - } + InetSocketAddressBaseList *p; + int i, cnt; + + 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 }; diff --git a/block/sheepdog.c b/block/sheepdog.c index d8c10b7cac..8680b2926f 100644 --- a/block/sheepdog.c +++ b/block/sheepdog.c @@ -15,8 +15,10 @@ #include "qemu/osdep.h" #include "qapi/error.h" #include "qapi/qapi-visit-sockets.h" +#include "qapi/qapi-visit-block-core.h" #include "qapi/qmp/qdict.h" #include "qapi/qobject-input-visitor.h" +#include "qapi/qobject-output-visitor.h" #include "qemu/uri.h" #include "qemu/error-report.h" #include "qemu/option.h" @@ -533,23 +535,6 @@ static void sd_aio_setup(SheepdogAIOCB *acb, BDRVSheepdogState *s, qemu_co_mutex_unlock(&s->queue_lock); } -static SocketAddress *sd_socket_address(const char *path, - const char *host, const char *port) -{ - SocketAddress *addr = g_new0(SocketAddress, 1); - - if (path) { - addr->type = SOCKET_ADDRESS_TYPE_UNIX; - addr->u.q_unix.path = g_strdup(path); - } else { - addr->type = SOCKET_ADDRESS_TYPE_INET; - addr->u.inet.host = g_strdup(host ?: SD_DEFAULT_ADDR); - addr->u.inet.port = g_strdup(port ?: stringify(SD_DEFAULT_PORT)); - } - - return addr; -} - static SocketAddress *sd_server_config(QDict *options, Error **errp) { QDict *server = NULL; @@ -1882,6 +1867,86 @@ out_with_err_set: return ret; } +static int sd_create_prealloc(BlockdevOptionsSheepdog *location, int64_t size, + Error **errp) +{ + BlockDriverState *bs; + Visitor *v; + QObject *obj = NULL; + QDict *qdict; + Error *local_err = NULL; + int ret; + + v = qobject_output_visitor_new(&obj); + visit_type_BlockdevOptionsSheepdog(v, NULL, &location, &local_err); + visit_free(v); + + if (local_err) { + error_propagate(errp, local_err); + qobject_decref(obj); + return -EINVAL; + } + + qdict = qobject_to_qdict(obj); + qdict_flatten(qdict); + + qdict_put_str(qdict, "driver", "sheepdog"); + + bs = bdrv_open(NULL, NULL, qdict, BDRV_O_PROTOCOL | BDRV_O_RDWR, errp); + if (bs == NULL) { + ret = -EIO; + goto fail; + } + + ret = sd_prealloc(bs, 0, size, errp); +fail: + bdrv_unref(bs); + QDECREF(qdict); + return ret; +} + +static int parse_redundancy(BDRVSheepdogState *s, SheepdogRedundancy *opt) +{ + struct SheepdogInode *inode = &s->inode; + + switch (opt->type) { + case SHEEPDOG_REDUNDANCY_TYPE_FULL: + if (opt->u.full.copies > SD_MAX_COPIES || opt->u.full.copies < 1) { + return -EINVAL; + } + inode->copy_policy = 0; + inode->nr_copies = opt->u.full.copies; + return 0; + + case SHEEPDOG_REDUNDANCY_TYPE_ERASURE_CODED: + { + int64_t copy = opt->u.erasure_coded.data_strips; + int64_t parity = opt->u.erasure_coded.parity_strips; + + if (copy != 2 && copy != 4 && copy != 8 && copy != 16) { + return -EINVAL; + } + + if (parity >= SD_EC_MAX_STRIP || parity < 1) { + return -EINVAL; + } + + /* + * 4 bits for parity and 4 bits for data. + * We have to compress upper data bits because it can't represent 16 + */ + inode->copy_policy = ((copy / 2) << 4) + parity; + inode->nr_copies = copy + parity; + return 0; + } + + default: + g_assert_not_reached(); + } + + return -EINVAL; +} + /* * Sheepdog support two kinds of redundancy, full replication and erasure * coding. @@ -1892,60 +1957,61 @@ out_with_err_set: * # create a erasure coded vdi with x data strips and y parity strips * -o redundancy=x:y (x must be one of {2,4,8,16} and 1 <= y < SD_EC_MAX_STRIP) */ -static int parse_redundancy(BDRVSheepdogState *s, const char *opt) +static SheepdogRedundancy *parse_redundancy_str(const char *opt) { - struct SheepdogInode *inode = &s->inode; + SheepdogRedundancy *redundancy; const char *n1, *n2; long copy, parity; char p[10]; + int ret; pstrcpy(p, sizeof(p), opt); n1 = strtok(p, ":"); n2 = strtok(NULL, ":"); if (!n1) { - return -EINVAL; + return NULL; } - copy = strtol(n1, NULL, 10); - /* FIXME fix error checking by switching to qemu_strtol() */ - if (copy > SD_MAX_COPIES || copy < 1) { - return -EINVAL; - } - if (!n2) { - inode->copy_policy = 0; - inode->nr_copies = copy; - return 0; + ret = qemu_strtol(n1, NULL, 10, ©); + if (ret < 0) { + return NULL; } - if (copy != 2 && copy != 4 && copy != 8 && copy != 16) { - return -EINVAL; - } + redundancy = g_new0(SheepdogRedundancy, 1); + if (!n2) { + *redundancy = (SheepdogRedundancy) { + .type = SHEEPDOG_REDUNDANCY_TYPE_FULL, + .u.full.copies = copy, + }; + } else { + ret = qemu_strtol(n2, NULL, 10, &parity); + if (ret < 0) { + return NULL; + } - parity = strtol(n2, NULL, 10); - /* FIXME fix error checking by switching to qemu_strtol() */ - if (parity >= SD_EC_MAX_STRIP || parity < 1) { - return -EINVAL; + *redundancy = (SheepdogRedundancy) { + .type = SHEEPDOG_REDUNDANCY_TYPE_ERASURE_CODED, + .u.erasure_coded = { + .data_strips = copy, + .parity_strips = parity, + }, + }; } - /* - * 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, diff --git a/block/ssh.c b/block/ssh.c index ff9929497d..ab3acf0c22 100644 --- a/block/ssh.c +++ b/block/ssh.c @@ -35,6 +35,7 @@ #include "qemu/sockets.h" #include "qemu/uri.h" #include "qapi/qapi-visit-sockets.h" +#include "qapi/qapi-visit-block-core.h" #include "qapi/qmp/qdict.h" #include "qapi/qmp/qstring.h" #include "qapi/qobject-input-visitor.h" @@ -430,31 +431,35 @@ check_host_key_hash(BDRVSSHState *s, const char *hash, } static int check_host_key(BDRVSSHState *s, const char *host, int port, - const char *host_key_check, Error **errp) + SshHostKeyCheck *hkc, Error **errp) { - /* host_key_check=no */ - if (strcmp(host_key_check, "no") == 0) { - 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); - } + SshHostKeyCheckMode mode; - /* 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); + if (hkc) { + mode = hkc->mode; + } else { + mode = SSH_HOST_KEY_CHECK_MODE_KNOWN_HOSTS; } - /* host_key_check=yes */ - if (strcmp(host_key_check, "yes") == 0) { + switch (mode) { + case SSH_HOST_KEY_CHECK_MODE_NONE: + return 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; } @@ -544,16 +549,6 @@ static QemuOptsList ssh_runtime_opts = { .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, .help = "Defines how and what to check the host key against", @@ -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; - - qdict_extract_subqdict(options, &addr, "server."); - if (!qdict_size(addr)) { - error_setg(errp, "SSH server address missing"); - goto out; + BlockdevOptionsSsh *result = NULL; + QemuOpts *opts = NULL; + Error *local_err = NULL; + QObject *crumpled; + const QDictEntry *e; + Visitor *v; + + /* 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 ssh_co_create(BlockdevCreateOptions *options, Error **errp) +{ + BlockdevCreateOptionsSsh *opts = &options->u.ssh; + BDRVSSHState s; + int ret; + + assert(options->driver == BLOCKDEV_DRIVER_SSH); + + ssh_state_init(&s); + + 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; + } + + if (opts->size > 0) { + ret = ssh_grow_file(&s, opts->size, errp); + if (ret < 0) { + 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) { - int r, ret; - int64_t total_size = 0; + BlockdevCreateOptions *create_options; + BlockdevCreateOptionsSsh *ssh_opts; + int ret; QDict *uri_options = NULL; - BDRVSSHState s; - ssh_state_init(&s); + create_options = g_new0(BlockdevCreateOptions, 1); + create_options->driver = BLOCKDEV_DRIVER_SSH; + ssh_opts = &create_options->u.ssh; /* 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); + 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(); - r = parse_uri(filename, uri_options, errp); - if (r < 0) { - ret = r; + ret = parse_uri(filename, uri_options, errp); + if (ret < 0) { goto out; } - 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; + ssh_opts->location = ssh_parse_options(uri_options, errp); + if (ssh_opts->location == NULL) { + ret = -EINVAL; goto out; } - if (total_size > 0) { - ret = ssh_grow_file(&s, total_size, errp); - if (ret < 0) { - goto out; - } - } - - ret = 0; + 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, diff --git a/block/vdi.c b/block/vdi.c index 68592cc58d..2b5ddd0666 100644 --- a/block/vdi.c +++ b/block/vdi.c @@ -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) diff --git a/block/vhdx.c b/block/vhdx.c index 3fbff5048b..d82350d07c 100644 --- a/block/vhdx.c +++ b/block/vhdx.c @@ -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, diff --git a/block/vmdk.c b/block/vmdk.c index 67342ed69b..f94c49a9c0 100644 --- a/block/vmdk.c +++ b/block/vmdk.c @@ -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, |