summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--block.c164
-rw-r--r--block/backup-top.c31
-rw-r--r--block/file-posix.c67
-rw-r--r--block/iscsi.c56
-rw-r--r--block/nbd.c14
-rw-r--r--block/qapi.c15
-rw-r--r--block/qcow2.c11
-rw-r--r--blockdev.c8
-rw-r--r--docs/interop/qcow2.txt64
-rw-r--r--docs/interop/qemu-img.rst9
-rw-r--r--include/block/block.h2
-rw-r--r--include/block/qapi.h4
-rw-r--r--monitor/hmp-cmds.c2
-rw-r--r--qapi/block-core.json7
-rw-r--r--qemu-img-cmds.hx4
-rw-r--r--qemu-img.c28
-rwxr-xr-xtests/qemu-iotests/12214
-rw-r--r--tests/qemu-iotests/122.out5
-rwxr-xr-xtests/qemu-iotests/1393
-rwxr-xr-xtests/qemu-iotests/1472
-rwxr-xr-xtests/qemu-iotests/25962
-rw-r--r--tests/qemu-iotests/259.out14
-rwxr-xr-xtests/qemu-iotests/2797
-rwxr-xr-xtests/qemu-iotests/28497
-rw-r--r--tests/qemu-iotests/284.out62
-rwxr-xr-xtests/qemu-iotests/28676
-rw-r--r--tests/qemu-iotests/286.out8
-rw-r--r--tests/qemu-iotests/group3
28 files changed, 659 insertions, 180 deletions
diff --git a/block.c b/block.c
index 946e3c896e..308a91c96b 100644
--- a/block.c
+++ b/block.c
@@ -532,20 +532,139 @@ out:
return ret;
}
-int bdrv_create_file(const char *filename, QemuOpts *opts, Error **errp)
+/**
+ * Helper function for bdrv_create_file_fallback(): Resize @blk to at
+ * least the given @minimum_size.
+ *
+ * On success, return @blk's actual length.
+ * Otherwise, return -errno.
+ */
+static int64_t create_file_fallback_truncate(BlockBackend *blk,
+ int64_t minimum_size, Error **errp)
{
- BlockDriver *drv;
+ Error *local_err = NULL;
+ int64_t size;
+ int ret;
+
+ ret = blk_truncate(blk, minimum_size, false, PREALLOC_MODE_OFF, &local_err);
+ if (ret < 0 && ret != -ENOTSUP) {
+ error_propagate(errp, local_err);
+ return ret;
+ }
+
+ size = blk_getlength(blk);
+ if (size < 0) {
+ error_free(local_err);
+ error_setg_errno(errp, -size,
+ "Failed to inquire the new image file's length");
+ return size;
+ }
+
+ if (size < minimum_size) {
+ /* Need to grow the image, but we failed to do that */
+ error_propagate(errp, local_err);
+ return -ENOTSUP;
+ }
+
+ error_free(local_err);
+ local_err = NULL;
+
+ return size;
+}
+
+/**
+ * Helper function for bdrv_create_file_fallback(): Zero the first
+ * sector to remove any potentially pre-existing image header.
+ */
+static int create_file_fallback_zero_first_sector(BlockBackend *blk,
+ int64_t current_size,
+ Error **errp)
+{
+ int64_t bytes_to_clear;
+ int ret;
+
+ bytes_to_clear = MIN(current_size, BDRV_SECTOR_SIZE);
+ if (bytes_to_clear) {
+ ret = blk_pwrite_zeroes(blk, 0, bytes_to_clear, BDRV_REQ_MAY_UNMAP);
+ if (ret < 0) {
+ error_setg_errno(errp, -ret,
+ "Failed to clear the new image's first sector");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int bdrv_create_file_fallback(const char *filename, BlockDriver *drv,
+ QemuOpts *opts, Error **errp)
+{
+ BlockBackend *blk;
+ QDict *options = qdict_new();
+ int64_t size = 0;
+ char *buf = NULL;
+ PreallocMode prealloc;
Error *local_err = NULL;
int ret;
+ size = qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0);
+ 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;
+ }
+
+ if (prealloc != PREALLOC_MODE_OFF) {
+ error_setg(errp, "Unsupported preallocation mode '%s'",
+ PreallocMode_str(prealloc));
+ return -ENOTSUP;
+ }
+
+ qdict_put_str(options, "driver", drv->format_name);
+
+ blk = blk_new_open(filename, NULL, options,
+ BDRV_O_RDWR | BDRV_O_RESIZE, errp);
+ if (!blk) {
+ error_prepend(errp, "Protocol driver '%s' does not support image "
+ "creation, and opening the image failed: ",
+ drv->format_name);
+ return -EINVAL;
+ }
+
+ size = create_file_fallback_truncate(blk, size, errp);
+ if (size < 0) {
+ ret = size;
+ goto out;
+ }
+
+ ret = create_file_fallback_zero_first_sector(blk, size, errp);
+ if (ret < 0) {
+ goto out;
+ }
+
+ ret = 0;
+out:
+ blk_unref(blk);
+ return ret;
+}
+
+int bdrv_create_file(const char *filename, QemuOpts *opts, Error **errp)
+{
+ BlockDriver *drv;
+
drv = bdrv_find_protocol(filename, true, errp);
if (drv == NULL) {
return -ENOENT;
}
- ret = bdrv_create(drv, filename, opts, &local_err);
- error_propagate(errp, local_err);
- return ret;
+ if (drv->bdrv_co_create_opts) {
+ return bdrv_create(drv, filename, opts, errp);
+ } else {
+ return bdrv_create_file_fallback(filename, drv, opts, errp);
+ }
}
/**
@@ -1444,6 +1563,24 @@ QemuOptsList bdrv_runtime_opts = {
},
};
+static QemuOptsList fallback_create_opts = {
+ .name = "fallback-create-opts",
+ .head = QTAILQ_HEAD_INITIALIZER(fallback_create_opts.head),
+ .desc = {
+ {
+ .name = BLOCK_OPT_SIZE,
+ .type = QEMU_OPT_SIZE,
+ .help = "Virtual disk size"
+ },
+ {
+ .name = BLOCK_OPT_PREALLOC,
+ .type = QEMU_OPT_STRING,
+ .help = "Preallocation mode (allowed values: off)"
+ },
+ { /* end of list */ }
+ }
+};
+
/*
* Common part for opening disk images and files
*
@@ -4807,14 +4944,15 @@ BlockDriverState *bdrv_find_node(const char *node_name)
}
/* Put this QMP function here so it can access the static graph_bdrv_states. */
-BlockDeviceInfoList *bdrv_named_nodes_list(Error **errp)
+BlockDeviceInfoList *bdrv_named_nodes_list(bool flat,
+ Error **errp)
{
BlockDeviceInfoList *list, *entry;
BlockDriverState *bs;
list = NULL;
QTAILQ_FOREACH(bs, &graph_bdrv_states, node_list) {
- BlockDeviceInfo *info = bdrv_block_device_info(NULL, bs, errp);
+ BlockDeviceInfo *info = bdrv_block_device_info(NULL, bs, flat, errp);
if (!info) {
qapi_free_BlockDeviceInfoList(list);
return NULL;
@@ -5771,15 +5909,13 @@ void bdrv_img_create(const char *filename, const char *fmt,
return;
}
- if (!proto_drv->create_opts) {
- error_setg(errp, "Protocol driver '%s' does not support image creation",
- proto_drv->format_name);
- return;
- }
-
/* Create parameter list */
create_opts = qemu_opts_append(create_opts, drv->create_opts);
- create_opts = qemu_opts_append(create_opts, proto_drv->create_opts);
+ if (proto_drv->create_opts) {
+ create_opts = qemu_opts_append(create_opts, proto_drv->create_opts);
+ } else {
+ create_opts = qemu_opts_append(create_opts, &fallback_create_opts);
+ }
opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
diff --git a/block/backup-top.c b/block/backup-top.c
index fa78f3256d..1bfb360bd3 100644
--- a/block/backup-top.c
+++ b/block/backup-top.c
@@ -48,11 +48,17 @@ static coroutine_fn int backup_top_co_preadv(
}
static coroutine_fn int backup_top_cbw(BlockDriverState *bs, uint64_t offset,
- uint64_t bytes)
+ uint64_t bytes, BdrvRequestFlags flags)
{
BDRVBackupTopState *s = bs->opaque;
- uint64_t end = QEMU_ALIGN_UP(offset + bytes, s->bcs->cluster_size);
- uint64_t off = QEMU_ALIGN_DOWN(offset, s->bcs->cluster_size);
+ uint64_t off, end;
+
+ if (flags & BDRV_REQ_WRITE_UNCHANGED) {
+ return 0;
+ }
+
+ off = QEMU_ALIGN_DOWN(offset, s->bcs->cluster_size);
+ end = QEMU_ALIGN_UP(offset + bytes, s->bcs->cluster_size);
return block_copy(s->bcs, off, end - off, NULL);
}
@@ -60,7 +66,7 @@ static coroutine_fn int backup_top_cbw(BlockDriverState *bs, uint64_t offset,
static int coroutine_fn backup_top_co_pdiscard(BlockDriverState *bs,
int64_t offset, int bytes)
{
- int ret = backup_top_cbw(bs, offset, bytes);
+ int ret = backup_top_cbw(bs, offset, bytes, 0);
if (ret < 0) {
return ret;
}
@@ -71,7 +77,7 @@ static int coroutine_fn backup_top_co_pdiscard(BlockDriverState *bs,
static int coroutine_fn backup_top_co_pwrite_zeroes(BlockDriverState *bs,
int64_t offset, int bytes, BdrvRequestFlags flags)
{
- int ret = backup_top_cbw(bs, offset, bytes);
+ int ret = backup_top_cbw(bs, offset, bytes, flags);
if (ret < 0) {
return ret;
}
@@ -84,11 +90,9 @@ static coroutine_fn int backup_top_co_pwritev(BlockDriverState *bs,
uint64_t bytes,
QEMUIOVector *qiov, int flags)
{
- if (!(flags & BDRV_REQ_WRITE_UNCHANGED)) {
- int ret = backup_top_cbw(bs, offset, bytes);
- if (ret < 0) {
- return ret;
- }
+ int ret = backup_top_cbw(bs, offset, bytes, flags);
+ if (ret < 0) {
+ return ret;
}
return bdrv_co_pwritev(bs->backing, offset, bytes, qiov, flags);
@@ -196,8 +200,13 @@ BlockDriverState *bdrv_backup_top_append(BlockDriverState *source,
return NULL;
}
- top->total_sectors = source->total_sectors;
state = top->opaque;
+ top->total_sectors = source->total_sectors;
+ top->supported_write_flags = BDRV_REQ_WRITE_UNCHANGED |
+ (BDRV_REQ_FUA & source->supported_write_flags);
+ top->supported_zero_flags = BDRV_REQ_WRITE_UNCHANGED |
+ ((BDRV_REQ_FUA | BDRV_REQ_MAY_UNMAP | BDRV_REQ_NO_FALLBACK) &
+ source->supported_zero_flags);
bdrv_ref(target);
state->target = bdrv_attach_child(top, target, "target", &child_file, errp);
diff --git a/block/file-posix.c b/block/file-posix.c
index ab82ee1a67..6345477112 100644
--- a/block/file-posix.c
+++ b/block/file-posix.c
@@ -3477,67 +3477,6 @@ static coroutine_fn int hdev_co_pwrite_zeroes(BlockDriverState *bs,
return raw_do_pwrite_zeroes(bs, offset, bytes, flags, true);
}
-static int coroutine_fn hdev_co_create_opts(const char *filename, QemuOpts *opts,
- Error **errp)
-{
- int fd;
- int ret = 0;
- struct stat stat_buf;
- int64_t total_size = 0;
- bool has_prefix;
-
- /* This function is used by both protocol block drivers and therefore either
- * of these prefixes may be given.
- * The return value has to be stored somewhere, otherwise this is an error
- * due to -Werror=unused-value. */
- has_prefix =
- strstart(filename, "host_device:", &filename) ||
- strstart(filename, "host_cdrom:" , &filename);
-
- (void)has_prefix;
-
- ret = raw_normalize_devicepath(&filename, errp);
- if (ret < 0) {
- return ret;
- }
-
- /* Read out options */
- total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
- BDRV_SECTOR_SIZE);
-
- fd = qemu_open(filename, O_WRONLY | O_BINARY);
- if (fd < 0) {
- ret = -errno;
- error_setg_errno(errp, -ret, "Could not open device");
- return ret;
- }
-
- if (fstat(fd, &stat_buf) < 0) {
- ret = -errno;
- error_setg_errno(errp, -ret, "Could not stat device");
- } else if (!S_ISBLK(stat_buf.st_mode) && !S_ISCHR(stat_buf.st_mode)) {
- error_setg(errp,
- "The given file is neither a block nor a character device");
- ret = -ENODEV;
- } else if (lseek(fd, 0, SEEK_END) < total_size) {
- error_setg(errp, "Device is too small");
- ret = -ENOSPC;
- }
-
- if (!ret && total_size) {
- uint8_t buf[BDRV_SECTOR_SIZE] = { 0 };
- int64_t zero_size = MIN(BDRV_SECTOR_SIZE, total_size);
- if (lseek(fd, 0, SEEK_SET) == -1) {
- ret = -errno;
- } else {
- ret = qemu_write_full(fd, buf, zero_size);
- ret = ret == zero_size ? 0 : -errno;
- }
- }
- qemu_close(fd);
- return ret;
-}
-
static BlockDriver bdrv_host_device = {
.format_name = "host_device",
.protocol_name = "host_device",
@@ -3550,8 +3489,6 @@ static BlockDriver bdrv_host_device = {
.bdrv_reopen_prepare = raw_reopen_prepare,
.bdrv_reopen_commit = raw_reopen_commit,
.bdrv_reopen_abort = raw_reopen_abort,
- .bdrv_co_create_opts = hdev_co_create_opts,
- .create_opts = &raw_create_opts,
.mutable_opts = mutable_opts,
.bdrv_co_invalidate_cache = raw_co_invalidate_cache,
.bdrv_co_pwrite_zeroes = hdev_co_pwrite_zeroes,
@@ -3678,8 +3615,6 @@ static BlockDriver bdrv_host_cdrom = {
.bdrv_reopen_prepare = raw_reopen_prepare,
.bdrv_reopen_commit = raw_reopen_commit,
.bdrv_reopen_abort = raw_reopen_abort,
- .bdrv_co_create_opts = hdev_co_create_opts,
- .create_opts = &raw_create_opts,
.mutable_opts = mutable_opts,
.bdrv_co_invalidate_cache = raw_co_invalidate_cache,
@@ -3812,8 +3747,6 @@ static BlockDriver bdrv_host_cdrom = {
.bdrv_reopen_prepare = raw_reopen_prepare,
.bdrv_reopen_commit = raw_reopen_commit,
.bdrv_reopen_abort = raw_reopen_abort,
- .bdrv_co_create_opts = hdev_co_create_opts,
- .create_opts = &raw_create_opts,
.mutable_opts = mutable_opts,
.bdrv_co_preadv = raw_co_preadv,
diff --git a/block/iscsi.c b/block/iscsi.c
index c8feaa2f0e..682abd8e09 100644
--- a/block/iscsi.c
+++ b/block/iscsi.c
@@ -2164,58 +2164,6 @@ static int coroutine_fn iscsi_co_truncate(BlockDriverState *bs, int64_t offset,
return 0;
}
-static int coroutine_fn iscsi_co_create_opts(const char *filename, QemuOpts *opts,
- Error **errp)
-{
- int ret = 0;
- int64_t total_size = 0;
- BlockDriverState *bs;
- IscsiLun *iscsilun = NULL;
- QDict *bs_options;
- Error *local_err = NULL;
-
- bs = bdrv_new();
-
- /* Read out options */
- total_size = DIV_ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
- BDRV_SECTOR_SIZE);
- bs->opaque = g_new0(struct IscsiLun, 1);
- iscsilun = bs->opaque;
-
- bs_options = qdict_new();
- iscsi_parse_filename(filename, bs_options, &local_err);
- if (local_err) {
- error_propagate(errp, local_err);
- ret = -EINVAL;
- } else {
- ret = iscsi_open(bs, bs_options, 0, NULL);
- }
- qobject_unref(bs_options);
-
- if (ret != 0) {
- goto out;
- }
- iscsi_detach_aio_context(bs);
- if (iscsilun->type != TYPE_DISK) {
- ret = -ENODEV;
- goto out;
- }
- if (bs->total_sectors < total_size) {
- ret = -ENOSPC;
- goto out;
- }
-
- ret = 0;
-out:
- if (iscsilun->iscsi != NULL) {
- iscsi_destroy_context(iscsilun->iscsi);
- }
- g_free(bs->opaque);
- bs->opaque = NULL;
- bdrv_unref(bs);
- return ret;
-}
-
static int iscsi_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
{
IscsiLun *iscsilun = bs->opaque;
@@ -2486,8 +2434,6 @@ static BlockDriver bdrv_iscsi = {
.bdrv_parse_filename = iscsi_parse_filename,
.bdrv_file_open = iscsi_open,
.bdrv_close = iscsi_close,
- .bdrv_co_create_opts = iscsi_co_create_opts,
- .create_opts = &iscsi_create_opts,
.bdrv_reopen_prepare = iscsi_reopen_prepare,
.bdrv_reopen_commit = iscsi_reopen_commit,
.bdrv_co_invalidate_cache = iscsi_co_invalidate_cache,
@@ -2525,8 +2471,6 @@ static BlockDriver bdrv_iser = {
.bdrv_parse_filename = iscsi_parse_filename,
.bdrv_file_open = iscsi_open,
.bdrv_close = iscsi_close,
- .bdrv_co_create_opts = iscsi_co_create_opts,
- .create_opts = &iscsi_create_opts,
.bdrv_reopen_prepare = iscsi_reopen_prepare,
.bdrv_reopen_commit = iscsi_reopen_commit,
.bdrv_co_invalidate_cache = iscsi_co_invalidate_cache,
diff --git a/block/nbd.c b/block/nbd.c
index d085554f21..6d3b22f844 100644
--- a/block/nbd.c
+++ b/block/nbd.c
@@ -70,6 +70,7 @@ typedef struct BDRVNBDState {
CoMutex send_mutex;
CoQueue free_sema;
Coroutine *connection_co;
+ Coroutine *teardown_co;
QemuCoSleepState *connection_co_sleep_ns_state;
bool drained;
bool wait_drained_end;
@@ -203,7 +204,15 @@ static void nbd_teardown_connection(BlockDriverState *bs)
qemu_co_sleep_wake(s->connection_co_sleep_ns_state);
}
}
- BDRV_POLL_WHILE(bs, s->connection_co);
+ if (qemu_in_coroutine()) {
+ s->teardown_co = qemu_coroutine_self();
+ /* connection_co resumes us when it terminates */
+ qemu_coroutine_yield();
+ s->teardown_co = NULL;
+ } else {
+ BDRV_POLL_WHILE(bs, s->connection_co);
+ }
+ assert(!s->connection_co);
}
static bool nbd_client_connecting(BDRVNBDState *s)
@@ -395,6 +404,9 @@ static coroutine_fn void nbd_connection_entry(void *opaque)
s->ioc = NULL;
}
+ if (s->teardown_co) {
+ aio_co_wake(s->teardown_co);
+ }
aio_wait_kick();
}
diff --git a/block/qapi.c b/block/qapi.c
index 9a5d0c9b27..afd9f3b4a7 100644
--- a/block/qapi.c
+++ b/block/qapi.c
@@ -42,7 +42,9 @@
#include "qemu/cutils.h"
BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk,
- BlockDriverState *bs, Error **errp)
+ BlockDriverState *bs,
+ bool flat,
+ Error **errp)
{
ImageInfo **p_image_info;
BlockDriverState *bs0;
@@ -156,6 +158,11 @@ BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk,
return NULL;
}
+ /* stop gathering data for flat output */
+ if (flat) {
+ break;
+ }
+
if (bs0->drv && bs0->backing) {
info->backing_file_depth++;
bs0 = bs0->backing->bs;
@@ -389,7 +396,7 @@ static void bdrv_query_info(BlockBackend *blk, BlockInfo **p_info,
if (bs && bs->drv) {
info->has_inserted = true;
- info->inserted = bdrv_block_device_info(blk, bs, errp);
+ info->inserted = bdrv_block_device_info(blk, bs, false, errp);
if (info->inserted == NULL) {
goto err;
}
@@ -657,7 +664,7 @@ void bdrv_snapshot_dump(QEMUSnapshotInfo *sn)
char *sizing = NULL;
if (!sn) {
- qemu_printf("%-10s%-20s%7s%20s%15s",
+ qemu_printf("%-10s%-20s%11s%20s%15s",
"ID", "TAG", "VM SIZE", "DATE", "VM CLOCK");
} else {
ti = sn->date_sec;
@@ -672,7 +679,7 @@ void bdrv_snapshot_dump(QEMUSnapshotInfo *sn)
(int)(secs % 60),
(int)((sn->vm_clock_nsec / 1000000) % 1000));
sizing = size_to_str(sn->vm_state_size);
- qemu_printf("%-10s%-20s%7s%20s%15s",
+ qemu_printf("%-10s%-20s%11s%20s%15s",
sn->id_str, sn->name,
sizing,
date_buf,
diff --git a/block/qcow2.c b/block/qcow2.c
index 8dcee5efec..3c754f616b 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -135,13 +135,16 @@ static ssize_t qcow2_crypto_hdr_init_func(QCryptoBlock *block, size_t headerlen,
s->crypto_header.length = headerlen;
s->crypto_header.offset = ret;
- /* Zero fill remaining space in cluster so it has predictable
- * content in case of future spec changes */
+ /*
+ * Zero fill all space in cluster so it has predictable
+ * content, as we may not initialize some regions of the
+ * header (eg only 1 out of 8 key slots will be initialized)
+ */
clusterlen = size_to_clusters(s, headerlen) * s->cluster_size;
assert(qcow2_pre_write_overlap_check(bs, 0, ret, clusterlen, false) == 0);
ret = bdrv_pwrite_zeroes(bs->file,
- ret + headerlen,
- clusterlen - headerlen, 0);
+ ret,
+ clusterlen, 0);
if (ret < 0) {
error_setg_errno(errp, -ret, "Could not zero fill encryption header");
return -1;
diff --git a/blockdev.c b/blockdev.c
index 45de0ba37e..011dcfec27 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -3734,9 +3734,13 @@ void qmp_drive_backup(DriveBackup *backup, Error **errp)
blockdev_do_action(&action, errp);
}
-BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp)
+BlockDeviceInfoList *qmp_query_named_block_nodes(bool has_flat,
+ bool flat,
+ Error **errp)
{
- return bdrv_named_nodes_list(errp);
+ bool return_flat = has_flat && flat;
+
+ return bdrv_named_nodes_list(return_flat, errp);
}
XDbgBlockGraph *qmp_x_debug_query_block_graph(Error **errp)
diff --git a/docs/interop/qcow2.txt b/docs/interop/qcow2.txt
index af5711e533..5597e24474 100644
--- a/docs/interop/qcow2.txt
+++ b/docs/interop/qcow2.txt
@@ -79,9 +79,9 @@ The first cluster of a qcow2 image contains the file header:
Offset into the image file at which the snapshot table
starts. Must be aligned to a cluster boundary.
-If the version is 3 or higher, the header has the following additional fields.
-For version 2, the values are assumed to be zero, unless specified otherwise
-in the description of a field.
+For version 2, the header is exactly 72 bytes in length, and finishes here.
+For version 3 or higher, the header length is at least 104 bytes, including
+the next fields through header_length.
72 - 79: incompatible_features
Bitmask of incompatible features. An implementation must
@@ -109,7 +109,12 @@ in the description of a field.
An External Data File Name header extension may
be present if this bit is set.
- Bits 3-63: Reserved (set to 0)
+ Bit 3: Compression type bit. If this bit is set,
+ a non-default compression is used for compressed
+ clusters. The compression_type field must be
+ present and not zero.
+
+ Bits 4-63: Reserved (set to 0)
80 - 87: compatible_features
Bitmask of compatible features. An implementation can
@@ -164,6 +169,57 @@ in the description of a field.
100 - 103: header_length
Length of the header structure in bytes. For version 2
images, the length is always assumed to be 72 bytes.
+ For version 3 it's at least 104 bytes and must be a multiple
+ of 8.
+
+
+=== Additional fields (version 3 and higher) ===
+
+In general, these fields are optional and may be safely ignored by the software,
+as well as filled by zeros (which is equal to field absence), if software needs
+to set field B, but does not care about field A which precedes B. More
+formally, additional fields have the following compatibility rules:
+
+1. If the value of the additional field must not be ignored for correct
+handling of the file, it will be accompanied by a corresponding incompatible
+feature bit.
+
+2. If there are no unrecognized incompatible feature bits set, an unknown
+additional field may be safely ignored other than preserving its value when
+rewriting the image header.
+
+3. An explicit value of 0 will have the same behavior as when the field is not
+present*, if not altered by a specific incompatible bit.
+
+*. A field is considered not present when header_length is less than or equal
+to the field's offset. Also, all additional fields are not present for
+version 2.
+
+ 104: compression_type
+
+ Defines the compression method used for compressed clusters.
+ All compressed clusters in an image use the same compression
+ type.
+
+ If the incompatible bit "Compression type" is set: the field
+ must be present and non-zero (which means non-zlib
+ compression type). Otherwise, this field must not be present
+ or must be zero (which means zlib).
+
+ Available compression type values:
+ 0: zlib <https://www.zlib.net/>
+
+
+=== Header padding ===
+
+@header_length must be a multiple of 8, which means that if the end of the last
+additional field is not aligned, some padding is needed. This padding must be
+zeroed, so that if some existing (or future) additional field will fall into
+the padding, it will be interpreted accordingly to point [3.] of the previous
+paragraph, i.e. in the same manner as when this field is not present.
+
+
+=== Header extensions ===
Directly after the image header, optional sections called header extensions can
be stored. Each extension has a structure like the following:
diff --git a/docs/interop/qemu-img.rst b/docs/interop/qemu-img.rst
index 42e4451db4..5f40137c10 100644
--- a/docs/interop/qemu-img.rst
+++ b/docs/interop/qemu-img.rst
@@ -214,6 +214,13 @@ Parameters to convert subcommand:
will still be printed. Areas that cannot be read from the source will be
treated as containing only zeroes.
+.. option:: --target-is-zero
+
+ Assume that reading the destination image will always return
+ zeros. This parameter is mutually exclusive with a destination image
+ that has a backing file. It is required to also use the ``-n``
+ parameter to skip image creation.
+
Parameters to dd subcommand:
.. program:: qemu-img-dd
@@ -366,7 +373,7 @@ Command description:
4
Error on reading data
-.. option:: convert [--object OBJECTDEF] [--image-opts] [--target-image-opts] [-U] [-C] [-c] [-p] [-q] [-n] [-f FMT] [-t CACHE] [-T SRC_CACHE] [-O OUTPUT_FMT] [-B BACKING_FILE] [-o OPTIONS] [-l SNAPSHOT_PARAM] [-S SPARSE_SIZE] [-m NUM_COROUTINES] [-W] FILENAME [FILENAME2 [...]] OUTPUT_FILENAME
+.. option:: convert [--object OBJECTDEF] [--image-opts] [--target-image-opts] [--target-is-zero] [-U] [-C] [-c] [-p] [-q] [-n] [-f FMT] [-t CACHE] [-T SRC_CACHE] [-O OUTPUT_FMT] [-B BACKING_FILE] [-o OPTIONS] [-l SNAPSHOT_PARAM] [-S SPARSE_SIZE] [-m NUM_COROUTINES] [-W] FILENAME [FILENAME2 [...]] OUTPUT_FILENAME
Convert the disk image *FILENAME* or a snapshot *SNAPSHOT_PARAM*
to disk image *OUTPUT_FILENAME* using format *OUTPUT_FMT*. It can
diff --git a/include/block/block.h b/include/block/block.h
index 314ce63ed9..cd6b5b95aa 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -454,7 +454,7 @@ void bdrv_lock_medium(BlockDriverState *bs, bool locked);
void bdrv_eject(BlockDriverState *bs, bool eject_flag);
const char *bdrv_get_format_name(BlockDriverState *bs);
BlockDriverState *bdrv_find_node(const char *node_name);
-BlockDeviceInfoList *bdrv_named_nodes_list(Error **errp);
+BlockDeviceInfoList *bdrv_named_nodes_list(bool flat, Error **errp);
XDbgBlockGraph *bdrv_get_xdbg_block_graph(Error **errp);
BlockDriverState *bdrv_lookup_bs(const char *device,
const char *node_name,
diff --git a/include/block/qapi.h b/include/block/qapi.h
index cd9410dee3..22c7807c89 100644
--- a/include/block/qapi.h
+++ b/include/block/qapi.h
@@ -29,7 +29,9 @@
#include "block/snapshot.h"
BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk,
- BlockDriverState *bs, Error **errp);
+ BlockDriverState *bs,
+ bool flat,
+ Error **errp);
int bdrv_query_snapshot_info_list(BlockDriverState *bs,
SnapshotInfoList **p_list,
Error **errp);
diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
index b237613e0d..53bc3f76c4 100644
--- a/monitor/hmp-cmds.c
+++ b/monitor/hmp-cmds.c
@@ -620,7 +620,7 @@ void hmp_info_block(Monitor *mon, const QDict *qdict)
}
/* Print node information */
- blockdev_list = qmp_query_named_block_nodes(NULL);
+ blockdev_list = qmp_query_named_block_nodes(false, false, NULL);
for (blockdev = blockdev_list; blockdev; blockdev = blockdev->next) {
assert(blockdev->value->has_node_name);
if (device && strcmp(device, blockdev->value->node_name)) {
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 37d7ea7295..85e27bb61f 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1758,6 +1758,9 @@
#
# Get the named block driver list
#
+# @flat: Omit the nested data about backing image ("backing-image" key) if true.
+# Default is false (Since 5.0)
+#
# Returns: the list of BlockDeviceInfo
#
# Since: 2.0
@@ -1811,7 +1814,9 @@
# } } ] }
#
##
-{ 'command': 'query-named-block-nodes', 'returns': [ 'BlockDeviceInfo' ] }
+{ 'command': 'query-named-block-nodes',
+ 'returns': [ 'BlockDeviceInfo' ],
+ 'data': { '*flat': 'bool' } }
##
# @XDbgBlockGraphNodeType:
diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx
index d7fbc6b1f4..c9c54de1df 100644
--- a/qemu-img-cmds.hx
+++ b/qemu-img-cmds.hx
@@ -39,9 +39,9 @@ SRST
ERST
DEF("convert", img_convert,
- "convert [--object objectdef] [--image-opts] [--target-image-opts] [-U] [-C] [-c] [-p] [-q] [-n] [-f fmt] [-t cache] [-T src_cache] [-O output_fmt] [-B backing_file] [-o options] [-l snapshot_param] [-S sparse_size] [-m num_coroutines] [-W] [--salvage] filename [filename2 [...]] output_filename")
+ "convert [--object objectdef] [--image-opts] [--target-image-opts] [--target-is-zero] [-U] [-C] [-c] [-p] [-q] [-n] [-f fmt] [-t cache] [-T src_cache] [-O output_fmt] [-B backing_file] [-o options] [-l snapshot_param] [-S sparse_size] [-m num_coroutines] [-W] [--salvage] filename [filename2 [...]] output_filename")
SRST
-.. option:: convert [--object OBJECTDEF] [--image-opts] [--target-image-opts] [-U] [-C] [-c] [-p] [-q] [-n] [-f FMT] [-t CACHE] [-T SRC_CACHE] [-O OUTPUT_FMT] [-B BACKING_FILE] [-o OPTIONS] [-l SNAPSHOT_PARAM] [-S SPARSE_SIZE] [-m NUM_COROUTINES] [-W] [--salvage] FILENAME [FILENAME2 [...]] OUTPUT_FILENAME
+.. option:: convert [--object OBJECTDEF] [--image-opts] [--target-image-opts] [--target-is-zero] [-U] [-C] [-c] [-p] [-q] [-n] [-f FMT] [-t CACHE] [-T SRC_CACHE] [-O OUTPUT_FMT] [-B BACKING_FILE] [-o OPTIONS] [-l SNAPSHOT_PARAM] [-S SPARSE_SIZE] [-m NUM_COROUTINES] [-W] [--salvage] FILENAME [FILENAME2 [...]] OUTPUT_FILENAME
ERST
DEF("create", img_create,
diff --git a/qemu-img.c b/qemu-img.c
index 2b4562b9d9..804630a368 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -70,6 +70,7 @@ enum {
OPTION_PREALLOCATION = 265,
OPTION_SHRINK = 266,
OPTION_SALVAGE = 267,
+ OPTION_TARGET_IS_ZERO = 268,
};
typedef enum OutputFormat {
@@ -1984,10 +1985,9 @@ static int convert_do_copy(ImgConvertState *s)
int64_t sector_num = 0;
/* Check whether we have zero initialisation or can get it efficiently */
- if (s->target_is_new && s->min_sparse && !s->target_has_backing) {
+ if (!s->has_zero_init && s->target_is_new && s->min_sparse &&
+ !s->target_has_backing) {
s->has_zero_init = bdrv_has_zero_init(blk_bs(s->target));
- } else {
- s->has_zero_init = false;
}
if (!s->has_zero_init && !s->target_has_backing &&
@@ -2086,6 +2086,7 @@ static int img_convert(int argc, char **argv)
{"force-share", no_argument, 0, 'U'},
{"target-image-opts", no_argument, 0, OPTION_TARGET_IMAGE_OPTS},
{"salvage", no_argument, 0, OPTION_SALVAGE},
+ {"target-is-zero", no_argument, 0, OPTION_TARGET_IS_ZERO},
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, ":hf:O:B:Cco:l:S:pt:T:qnm:WU",
@@ -2209,6 +2210,14 @@ static int img_convert(int argc, char **argv)
case OPTION_TARGET_IMAGE_OPTS:
tgt_image_opts = true;
break;
+ case OPTION_TARGET_IS_ZERO:
+ /*
+ * The user asserting that the target is blank has the
+ * same effect as the target driver supporting zero
+ * initialisation.
+ */
+ s.has_zero_init = true;
+ break;
}
}
@@ -2247,6 +2256,11 @@ static int img_convert(int argc, char **argv)
warn_report("This will become an error in future QEMU versions.");
}
+ if (s.has_zero_init && !skip_create) {
+ error_report("--target-is-zero requires use of -n flag");
+ goto fail_getopt;
+ }
+
s.src_num = argc - optind - 1;
out_filename = s.src_num >= 1 ? argv[argc - 1] : NULL;
@@ -2380,6 +2394,12 @@ static int img_convert(int argc, char **argv)
}
s.target_has_backing = (bool) out_baseimg;
+ if (s.has_zero_init && s.target_has_backing) {
+ error_report("Cannot use --target-is-zero when the destination "
+ "image has a backing file");
+ goto out;
+ }
+
if (s.src_num > 1 && out_baseimg) {
error_report("Having a backing file for the target makes no sense when "
"concatenating multiple input images");
@@ -2503,7 +2523,7 @@ static int img_convert(int argc, char **argv)
}
}
- if (s.target_has_backing) {
+ if (s.target_has_backing && s.target_is_new) {
/* Errors are treated as "backing length unknown" (which means
* s.target_backing_sectors has to be negative, which it will
* be automatically). The backing file length is used only
diff --git a/tests/qemu-iotests/122 b/tests/qemu-iotests/122
index dfa350936f..f7a3ae684a 100755
--- a/tests/qemu-iotests/122
+++ b/tests/qemu-iotests/122
@@ -276,6 +276,20 @@ $QEMU_IMG convert -O $IMGFMT -n "$TEST_IMG" "$TEST_IMG".orig
$QEMU_IMG compare "$TEST_IMG" "$TEST_IMG".orig
+echo
+echo '=== -n -B to an image without a backing file ==='
+echo
+
+# Base for the output
+TEST_IMG="$TEST_IMG".base _make_test_img 64M
+
+# Output that does have $TEST_IMG.base set as its (implicit) backing file
+TEST_IMG="$TEST_IMG".orig _make_test_img 64M
+
+# Convert with -n, which should not confuse -B with "target BDS has a
+# backing file"
+$QEMU_IMG convert -O $IMGFMT -B "$TEST_IMG".base -n "$TEST_IMG" "$TEST_IMG".orig
+
# success, all done
echo '*** done'
rm -f $seq.full
diff --git a/tests/qemu-iotests/122.out b/tests/qemu-iotests/122.out
index 849b6cc2ef..1a35951a80 100644
--- a/tests/qemu-iotests/122.out
+++ b/tests/qemu-iotests/122.out
@@ -228,4 +228,9 @@ Formatting 'TEST_DIR/t.IMGFMT.orig', fmt=IMGFMT size=67108864
wrote 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
Images are identical.
+
+=== -n -B to an image without a backing file ===
+
+Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864
+Formatting 'TEST_DIR/t.IMGFMT.orig', fmt=IMGFMT size=67108864
*** done
diff --git a/tests/qemu-iotests/139 b/tests/qemu-iotests/139
index 6b1a444364..7120d3142b 100755
--- a/tests/qemu-iotests/139
+++ b/tests/qemu-iotests/139
@@ -344,9 +344,6 @@ class TestBlockdevDel(iotests.QMPTestCase):
@iotests.skip_if_unsupported(['quorum'])
def testQuorum(self):
- if not iotests.supports_quorum():
- return
-
self.addQuorum('quorum0', 'node0', 'node1')
# We cannot remove the children of a Quorum device
self.delBlockDriverState('node0', expect_error = True)
diff --git a/tests/qemu-iotests/147 b/tests/qemu-iotests/147
index f4b0a11dba..d7a9f31089 100755
--- a/tests/qemu-iotests/147
+++ b/tests/qemu-iotests/147
@@ -134,7 +134,7 @@ class BuiltinNBD(NBDBlockdevAddBase):
self.server.add_drive_raw('if=none,id=nbd-export,' +
'file=%s,' % test_img +
'format=%s,' % imgfmt +
- 'cache=%s' % cachemode +
+ 'cache=%s,' % cachemode +
'aio=%s' % aiomode)
self.server.launch()
diff --git a/tests/qemu-iotests/259 b/tests/qemu-iotests/259
new file mode 100755
index 0000000000..62e29af05f
--- /dev/null
+++ b/tests/qemu-iotests/259
@@ -0,0 +1,62 @@
+#!/usr/bin/env bash
+#
+# Test generic image creation fallback (by using NBD)
+#
+# Copyright (C) 2019 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# creator
+owner=mreitz@redhat.com
+
+seq=$(basename $0)
+echo "QA output created by $seq"
+
+status=1 # failure is the default!
+
+_cleanup()
+{
+ _cleanup_test_img
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+
+_supported_fmt raw
+_supported_proto nbd
+_supported_os Linux
+
+
+_make_test_img 64M
+
+echo
+echo '--- Testing creation ---'
+
+$QEMU_IMG create -f qcow2 "$TEST_IMG" 64M | _filter_img_create
+$QEMU_IMG info "$TEST_IMG" | _filter_img_info
+
+echo
+echo '--- Testing creation for which the node would need to grow ---'
+
+# NBD does not support resizing, so this will fail
+$QEMU_IMG create -f qcow2 -o preallocation=metadata "$TEST_IMG" 64M 2>&1 \
+ | _filter_img_create
+
+# success, all done
+echo "*** done"
+rm -f $seq.full
+status=0
diff --git a/tests/qemu-iotests/259.out b/tests/qemu-iotests/259.out
new file mode 100644
index 0000000000..ffed19c2a0
--- /dev/null
+++ b/tests/qemu-iotests/259.out
@@ -0,0 +1,14 @@
+QA output created by 259
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
+
+--- Testing creation ---
+Formatting 'TEST_DIR/t.IMGFMT', fmt=qcow2 size=67108864
+image: TEST_DIR/t.IMGFMT
+file format: qcow2
+virtual size: 64 MiB (67108864 bytes)
+disk size: unavailable
+
+--- Testing creation for which the node would need to grow ---
+qemu-img: TEST_DIR/t.IMGFMT: Could not resize image: Image format driver does not support resize
+Formatting 'TEST_DIR/t.IMGFMT', fmt=qcow2 size=67108864 preallocation=metadata
+*** done
diff --git a/tests/qemu-iotests/279 b/tests/qemu-iotests/279
index 6682376808..30d29b1cb2 100755
--- a/tests/qemu-iotests/279
+++ b/tests/qemu-iotests/279
@@ -38,6 +38,8 @@ trap "_cleanup; exit \$status" 0 1 2 3 15
_supported_fmt qcow qcow2 vmdk qed
_supported_proto file
_supported_os Linux
+_unsupported_imgopts "subformat=monolithicFlat" \
+ "subformat=twoGbMaxExtentFlat" \
TEST_IMG="$TEST_IMG.base" _make_test_img 64M
TEST_IMG="$TEST_IMG.mid" _make_test_img -b "$TEST_IMG.base"
@@ -45,11 +47,12 @@ _make_test_img -b "$TEST_IMG.mid"
echo
echo '== qemu-img info --backing-chain =='
-_img_info --backing-chain | _filter_img_info
+_img_info --backing-chain | _filter_img_info | grep -v 'backing file format'
echo
echo '== qemu-img info --backing-chain --image-opts =='
-TEST_IMG="driver=qcow2,file.driver=file,file.filename=$TEST_IMG" _img_info --backing-chain --image-opts | _filter_img_info
+TEST_IMG="driver=$IMGFMT,file.driver=file,file.filename=$TEST_IMG" _img_info --backing-chain --image-opts \
+ | _filter_img_info | grep -v 'backing file format'
# success, all done
echo "*** done"
diff --git a/tests/qemu-iotests/284 b/tests/qemu-iotests/284
new file mode 100755
index 0000000000..071e89b33e
--- /dev/null
+++ b/tests/qemu-iotests/284
@@ -0,0 +1,97 @@
+#!/usr/bin/env bash
+#
+# Test ref count checks on encrypted images
+#
+# Copyright (C) 2019 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# creator
+owner=berrange@redhat.com
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+status=1 # failure is the default!
+
+_cleanup()
+{
+ _cleanup_test_img
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+
+_supported_fmt qcow2
+_supported_proto generic
+_supported_os Linux
+
+
+size=1M
+
+SECRET="secret,id=sec0,data=astrochicken"
+
+IMGSPEC="driver=$IMGFMT,file.filename=$TEST_IMG,encrypt.key-secret=sec0"
+QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT
+
+_run_test()
+{
+ IMGOPTSSYNTAX=true
+ OLD_TEST_IMG="$TEST_IMG"
+ TEST_IMG="driver=$IMGFMT,file.filename=$TEST_IMG,encrypt.key-secret=sec0"
+ QEMU_IMG_EXTRA_ARGS="--image-opts --object $SECRET"
+
+ echo
+ echo "== cluster size $csize"
+ echo "== checking image refcounts =="
+ _check_test_img
+
+ echo
+ echo "== writing some data =="
+ $QEMU_IO -c "write -P 0x9 0 1" $QEMU_IMG_EXTRA_ARGS $TEST_IMG | _filter_qemu_io | _filter_testdir
+ echo
+ echo "== rechecking image refcounts =="
+ _check_test_img
+
+ echo
+ echo "== writing some more data =="
+ $QEMU_IO -c "write -P 0x9 $csize 1" $QEMU_IMG_EXTRA_ARGS $TEST_IMG | _filter_qemu_io | _filter_testdir
+ echo
+ echo "== rechecking image refcounts =="
+ _check_test_img
+
+ TEST_IMG="$OLD_TEST_IMG"
+ QEMU_IMG_EXTRA_ARGS=
+ IMGOPTSSYNTAX=
+}
+
+
+echo
+echo "testing LUKS qcow2 encryption"
+echo
+
+for csize in 512 2048 32768
+do
+ _make_test_img --object $SECRET -o "encrypt.format=luks,encrypt.key-secret=sec0,encrypt.iter-time=10,cluster_size=$csize" $size
+ _run_test
+ _cleanup_test_img
+done
+
+# success, all done
+echo "*** done"
+rm -f $seq.full
+status=0
diff --git a/tests/qemu-iotests/284.out b/tests/qemu-iotests/284.out
new file mode 100644
index 0000000000..48216f5742
--- /dev/null
+++ b/tests/qemu-iotests/284.out
@@ -0,0 +1,62 @@
+QA output created by 284
+
+testing LUKS qcow2 encryption
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10
+
+== cluster size 512
+== checking image refcounts ==
+No errors were found on the image.
+
+== writing some data ==
+wrote 1/1 bytes at offset 0
+1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== rechecking image refcounts ==
+No errors were found on the image.
+
+== writing some more data ==
+wrote 1/1 bytes at offset 512
+1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== rechecking image refcounts ==
+No errors were found on the image.
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10
+
+== cluster size 2048
+== checking image refcounts ==
+No errors were found on the image.
+
+== writing some data ==
+wrote 1/1 bytes at offset 0
+1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== rechecking image refcounts ==
+No errors were found on the image.
+
+== writing some more data ==
+wrote 1/1 bytes at offset 2048
+1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== rechecking image refcounts ==
+No errors were found on the image.
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10
+
+== cluster size 32768
+== checking image refcounts ==
+No errors were found on the image.
+
+== writing some data ==
+wrote 1/1 bytes at offset 0
+1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== rechecking image refcounts ==
+No errors were found on the image.
+
+== writing some more data ==
+wrote 1/1 bytes at offset 32768
+1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== rechecking image refcounts ==
+No errors were found on the image.
+*** done
diff --git a/tests/qemu-iotests/286 b/tests/qemu-iotests/286
new file mode 100755
index 0000000000..f14445ba4a
--- /dev/null
+++ b/tests/qemu-iotests/286
@@ -0,0 +1,76 @@
+#!/usr/bin/env bash
+#
+# Test qemu-img snapshot -l
+#
+# Copyright (C) 2019 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+seq=$(basename "$0")
+echo "QA output created by $seq"
+
+status=1 # failure is the default!
+
+_cleanup()
+{
+ _cleanup_test_img
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+. ./common.qemu
+
+_supported_fmt qcow2
+_supported_proto file
+# Internal snapshots are (currently) impossible with refcount_bits=1,
+# and generally impossible with external data files
+_unsupported_imgopts 'refcount_bits=1[^0-9]' data_file
+
+_make_test_img 64M
+
+# Should be so long as to take up the whole field width
+sn_name=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
+
+# More memory will give us a larger VM state, i.e. one above 1 MB.
+# This way, we get a number with a decimal point.
+qemu_comm_method=monitor _launch_qemu -m 512 "$TEST_IMG"
+
+_send_qemu_cmd $QEMU_HANDLE "savevm $sn_name" '(qemu)'
+_send_qemu_cmd $QEMU_HANDLE 'quit' '(qemu)'
+wait=yes _cleanup_qemu
+
+# Check that all fields are separated by spaces.
+# We first collapse all space sequences into one space each;
+# then we turn every space-separated field into a '.';
+# and finally, we name the '.'s so the output is not just a confusing
+# sequence of dots.
+
+echo 'Output structure:'
+$QEMU_IMG snapshot -l "$TEST_IMG" | tail -n 1 | tr -s ' ' \
+ | sed -e 's/\S\+/./g' \
+ | sed -e 's/\./(snapshot ID)/' \
+ -e 's/\./(snapshot name)/' \
+ -e 's/\./(VM state size value)/' \
+ -e 's/\./(VM state size unit)/' \
+ -e 's/\./(snapshot date)/' \
+ -e 's/\./(snapshot time)/' \
+ -e 's/\./(VM clock)/'
+
+# success, all done
+echo "*** done"
+rm -f $seq.full
+status=0
diff --git a/tests/qemu-iotests/286.out b/tests/qemu-iotests/286.out
new file mode 100644
index 0000000000..39ff07e12c
--- /dev/null
+++ b/tests/qemu-iotests/286.out
@@ -0,0 +1,8 @@
+QA output created by 286
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
+QEMU X.Y.Z monitor - type 'help' for more information
+(qemu) savevm abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
+(qemu) quit
+Output structure:
+(snapshot ID) (snapshot name) (VM state size value) (VM state size unit) (snapshot date) (snapshot time) (VM clock)
+*** done
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index 1904223020..0317667695 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -273,6 +273,7 @@
256 rw auto quick
257 rw
258 rw quick
+259 rw auto quick
260 rw quick
261 rw
262 rw quick migration
@@ -290,3 +291,5 @@
280 rw migration quick
281 rw quick
283 auto quick
+284 rw
+286 rw quick