aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--block.c51
-rw-r--r--block/block-backend.c48
-rw-r--r--block/io.c36
-rw-r--r--block/qapi.c8
-rw-r--r--block/qcow2-cache.c23
-rw-r--r--block/qcow2-cluster.c13
-rw-r--r--block/qcow2-refcount.c26
-rw-r--r--block/qcow2.c9
-rw-r--r--block/qcow2.h6
-rw-r--r--block/replication.c15
-rw-r--r--block/snapshot.c6
-rw-r--r--block/vvfat.c2
-rw-r--r--include/block/block.h1
-rw-r--r--include/qapi/qmp/qbool.h1
-rw-r--r--include/qapi/qmp/qdict.h2
-rw-r--r--include/qapi/qmp/qlist.h4
-rw-r--r--include/qapi/qmp/qnull.h32
-rw-r--r--include/qapi/qmp/qnum.h1
-rw-r--r--include/qapi/qmp/qobject.h21
-rw-r--r--include/qapi/qmp/qstring.h1
-rw-r--r--include/qapi/qmp/types.h1
-rw-r--r--migration/block.c1
-rw-r--r--qapi/qapi-clone-visitor.c1
-rw-r--r--qapi/string-input-visitor.c1
-rw-r--r--qobject/qbool.c8
-rw-r--r--qobject/qdict.c29
-rw-r--r--qobject/qlist.c32
-rw-r--r--qobject/qnull.c11
-rw-r--r--qobject/qnum.c54
-rw-r--r--qobject/qobject.c29
-rw-r--r--qobject/qstring.c9
-rw-r--r--scripts/coccinelle/qobject.cocci3
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/Makefile.include4
-rw-r--r--tests/check-qnull.c2
-rw-r--r--tests/check-qobject.c328
-rwxr-xr-xtests/qemu-iotests/02027
-rw-r--r--tests/qemu-iotests/020.out17
-rwxr-xr-xtests/qemu-iotests/060125
-rw-r--r--tests/qemu-iotests/060.out115
-rwxr-xr-xtests/qemu-iotests/0879
-rwxr-xr-xtests/qemu-iotests/1339
-rw-r--r--tests/qemu-iotests/133.out5
43 files changed, 1085 insertions, 42 deletions
diff --git a/block.c b/block.c
index 752fe6192b..6c8ef98dfa 100644
--- a/block.c
+++ b/block.c
@@ -720,6 +720,10 @@ static int refresh_total_sectors(BlockDriverState *bs, int64_t hint)
{
BlockDriver *drv = bs->drv;
+ if (!drv) {
+ return -ENOMEDIUM;
+ }
+
/* Do not attempt drv->bdrv_getlength() on scsi-generic devices */
if (bdrv_is_sg(bs))
return 0;
@@ -3074,19 +3078,26 @@ int bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue,
const QDictEntry *entry = qdict_first(reopen_state->options);
do {
- QString *new_obj = qobject_to_qstring(entry->value);
- const char *new = qstring_get_str(new_obj);
+ QObject *new = entry->value;
+ QObject *old = qdict_get(reopen_state->bs->options, entry->key);
+
/*
- * Caution: while qdict_get_try_str() is fine, getting
- * non-string types would require more care. When
- * bs->options come from -blockdev or blockdev_add, its
- * members are typed according to the QAPI schema, but
- * when they come from -drive, they're all QString.
+ * TODO: When using -drive to specify blockdev options, all values
+ * will be strings; however, when using -blockdev, blockdev-add or
+ * filenames using the json:{} pseudo-protocol, they will be
+ * correctly typed.
+ * In contrast, reopening options are (currently) always strings
+ * (because you can only specify them through qemu-io; all other
+ * callers do not specify any options).
+ * Therefore, when using anything other than -drive to create a BDS,
+ * this cannot detect non-string options as unchanged, because
+ * qobject_is_equal() always returns false for objects of different
+ * type. In the future, this should be remedied by correctly typing
+ * all options. For now, this is not too big of an issue because
+ * the user can simply omit options which cannot be changed anyway,
+ * so they will stay unchanged.
*/
- const char *old = qdict_get_try_str(reopen_state->bs->options,
- entry->key);
-
- if (!old || strcmp(new, old)) {
+ if (!qobject_is_equal(new, old)) {
error_setg(errp, "Cannot change the option '%s'", entry->key);
ret = -EINVAL;
goto error;
@@ -3424,6 +3435,10 @@ int bdrv_change_backing_file(BlockDriverState *bs,
BlockDriver *drv = bs->drv;
int ret;
+ if (!drv) {
+ return -ENOMEDIUM;
+ }
+
/* Backing file format doesn't make sense without a backing file */
if (backing_fmt && !backing_file) {
return -EINVAL;
@@ -3909,7 +3924,9 @@ int bdrv_has_zero_init_1(BlockDriverState *bs)
int bdrv_has_zero_init(BlockDriverState *bs)
{
- assert(bs->drv);
+ if (!bs->drv) {
+ return 0;
+ }
/* If BS is a copy on write image, it is initialized to
the contents of the base image, which may not be zeroes. */
@@ -4238,6 +4255,7 @@ void bdrv_invalidate_cache_all(Error **errp)
aio_context_release(aio_context);
if (local_err) {
error_propagate(errp, local_err);
+ bdrv_next_cleanup(&it);
return;
}
}
@@ -4249,6 +4267,10 @@ static int bdrv_inactivate_recurse(BlockDriverState *bs,
BdrvChild *child, *parent;
int ret;
+ if (!bs->drv) {
+ return -ENOMEDIUM;
+ }
+
if (!setting_flag && bs->drv->bdrv_inactivate) {
ret = bs->drv->bdrv_inactivate(bs);
if (ret < 0) {
@@ -4309,6 +4331,7 @@ int bdrv_inactivate_all(void)
for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
ret = bdrv_inactivate_recurse(bs, pass);
if (ret < 0) {
+ bdrv_next_cleanup(&it);
goto out;
}
}
@@ -4783,6 +4806,9 @@ void bdrv_remove_aio_context_notifier(BlockDriverState *bs,
int bdrv_amend_options(BlockDriverState *bs, QemuOpts *opts,
BlockDriverAmendStatusCB *status_cb, void *cb_opaque)
{
+ if (!bs->drv) {
+ return -ENOMEDIUM;
+ }
if (!bs->drv->bdrv_amend_options) {
return -ENOTSUP;
}
@@ -4840,6 +4866,7 @@ bool bdrv_is_first_non_filter(BlockDriverState *candidate)
/* candidate is the first non filter */
if (perm) {
+ bdrv_next_cleanup(&it);
return true;
}
}
diff --git a/block/block-backend.c b/block/block-backend.c
index f10b1db612..5836cb3087 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -442,21 +442,37 @@ BlockBackend *blk_next(BlockBackend *blk)
* the monitor or attached to a BlockBackend */
BlockDriverState *bdrv_next(BdrvNextIterator *it)
{
- BlockDriverState *bs;
+ BlockDriverState *bs, *old_bs;
+
+ /* Must be called from the main loop */
+ assert(qemu_get_current_aio_context() == qemu_get_aio_context());
/* First, return all root nodes of BlockBackends. In order to avoid
* returning a BDS twice when multiple BBs refer to it, we only return it
* if the BB is the first one in the parent list of the BDS. */
if (it->phase == BDRV_NEXT_BACKEND_ROOTS) {
+ BlockBackend *old_blk = it->blk;
+
+ old_bs = old_blk ? blk_bs(old_blk) : NULL;
+
do {
it->blk = blk_all_next(it->blk);
bs = it->blk ? blk_bs(it->blk) : NULL;
} while (it->blk && (bs == NULL || bdrv_first_blk(bs) != it->blk));
+ if (it->blk) {
+ blk_ref(it->blk);
+ }
+ blk_unref(old_blk);
+
if (bs) {
+ bdrv_ref(bs);
+ bdrv_unref(old_bs);
return bs;
}
it->phase = BDRV_NEXT_MONITOR_OWNED;
+ } else {
+ old_bs = it->bs;
}
/* Then return the monitor-owned BDSes without a BB attached. Ignore all
@@ -467,18 +483,46 @@ BlockDriverState *bdrv_next(BdrvNextIterator *it)
bs = it->bs;
} while (bs && bdrv_has_blk(bs));
+ if (bs) {
+ bdrv_ref(bs);
+ }
+ bdrv_unref(old_bs);
+
return bs;
}
-BlockDriverState *bdrv_first(BdrvNextIterator *it)
+static void bdrv_next_reset(BdrvNextIterator *it)
{
*it = (BdrvNextIterator) {
.phase = BDRV_NEXT_BACKEND_ROOTS,
};
+}
+BlockDriverState *bdrv_first(BdrvNextIterator *it)
+{
+ bdrv_next_reset(it);
return bdrv_next(it);
}
+/* Must be called when aborting a bdrv_next() iteration before
+ * bdrv_next() returns NULL */
+void bdrv_next_cleanup(BdrvNextIterator *it)
+{
+ /* Must be called from the main loop */
+ assert(qemu_get_current_aio_context() == qemu_get_aio_context());
+
+ if (it->phase == BDRV_NEXT_BACKEND_ROOTS) {
+ if (it->blk) {
+ bdrv_unref(blk_bs(it->blk));
+ blk_unref(it->blk);
+ }
+ } else {
+ bdrv_unref(it->bs);
+ }
+
+ bdrv_next_reset(it);
+}
+
/*
* Add a BlockBackend into the list of backends referenced by the monitor, with
* the given @name acting as the handle for the monitor.
diff --git a/block/io.c b/block/io.c
index 3d5ef2cabe..4fdf93a014 100644
--- a/block/io.c
+++ b/block/io.c
@@ -853,6 +853,10 @@ static int coroutine_fn bdrv_driver_preadv(BlockDriverState *bs,
assert(!(flags & ~BDRV_REQ_MASK));
+ if (!drv) {
+ return -ENOMEDIUM;
+ }
+
if (drv->bdrv_co_preadv) {
return drv->bdrv_co_preadv(bs, offset, bytes, qiov, flags);
}
@@ -894,6 +898,10 @@ static int coroutine_fn bdrv_driver_pwritev(BlockDriverState *bs,
assert(!(flags & ~BDRV_REQ_MASK));
+ if (!drv) {
+ return -ENOMEDIUM;
+ }
+
if (drv->bdrv_co_pwritev) {
ret = drv->bdrv_co_pwritev(bs, offset, bytes, qiov,
flags & bs->supported_write_flags);
@@ -945,6 +953,10 @@ bdrv_driver_pwritev_compressed(BlockDriverState *bs, uint64_t offset,
{
BlockDriver *drv = bs->drv;
+ if (!drv) {
+ return -ENOMEDIUM;
+ }
+
if (!drv->bdrv_co_pwritev_compressed) {
return -ENOTSUP;
}
@@ -975,6 +987,10 @@ static int coroutine_fn bdrv_co_do_copy_on_readv(BdrvChild *child,
BDRV_REQUEST_MAX_BYTES);
unsigned int progress = 0;
+ if (!drv) {
+ return -ENOMEDIUM;
+ }
+
/* FIXME We cannot require callers to have write permissions when all they
* are doing is a read request. If we did things right, write permissions
* would be obtained anyway, but internally by the copy-on-read code. As
@@ -1291,6 +1307,10 @@ static int coroutine_fn bdrv_co_do_pwrite_zeroes(BlockDriverState *bs,
bs->bl.request_alignment);
int max_transfer = MIN_NON_ZERO(bs->bl.max_transfer, MAX_BOUNCE_BUFFER);
+ if (!drv) {
+ return -ENOMEDIUM;
+ }
+
assert(alignment % bs->bl.request_alignment == 0);
head = offset % alignment;
tail = (offset + bytes) % alignment;
@@ -1397,6 +1417,10 @@ static int coroutine_fn bdrv_aligned_pwritev(BdrvChild *child,
uint64_t bytes_remaining = bytes;
int max_transfer;
+ if (!drv) {
+ return -ENOMEDIUM;
+ }
+
if (bdrv_has_readonly_bitmaps(bs)) {
return -EPERM;
}
@@ -1863,6 +1887,8 @@ static int coroutine_fn bdrv_co_block_status(BlockDriverState *bs,
bytes = n;
}
+ /* Must be non-NULL or bdrv_getlength() would have failed */
+ assert(bs->drv);
if (!bs->drv->bdrv_co_get_block_status) {
*pnum = bytes;
ret = BDRV_BLOCK_DATA | BDRV_BLOCK_ALLOCATED;
@@ -2373,6 +2399,12 @@ int coroutine_fn bdrv_co_flush(BlockDriverState *bs)
}
BLKDBG_EVENT(bs->file, BLKDBG_FLUSH_TO_DISK);
+ if (!bs->drv) {
+ /* bs->drv->bdrv_co_flush() might have ejected the BDS
+ * (even in case of apparent success) */
+ ret = -ENOMEDIUM;
+ goto out;
+ }
if (bs->drv->bdrv_co_flush_to_disk) {
ret = bs->drv->bdrv_co_flush_to_disk(bs);
} else if (bs->drv->bdrv_aio_flush) {
@@ -2542,6 +2574,10 @@ int coroutine_fn bdrv_co_pdiscard(BlockDriverState *bs, int64_t offset,
num = max_pdiscard;
}
+ if (!bs->drv) {
+ ret = -ENOMEDIUM;
+ goto out;
+ }
if (bs->drv->bdrv_co_pdiscard) {
ret = bs->drv->bdrv_co_pdiscard(bs, offset, num);
} else {
diff --git a/block/qapi.c b/block/qapi.c
index 7fa2437923..fc10f0a565 100644
--- a/block/qapi.c
+++ b/block/qapi.c
@@ -39,8 +39,14 @@ BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk,
{
ImageInfo **p_image_info;
BlockDriverState *bs0;
- BlockDeviceInfo *info = g_malloc0(sizeof(*info));
+ BlockDeviceInfo *info;
+ if (!bs->drv) {
+ error_setg(errp, "Block device %s is ejected", bs->node_name);
+ return NULL;
+ }
+
+ info = g_malloc0(sizeof(*info));
info->file = g_strdup(bs->filename);
info->ro = bs->read_only;
info->drv = g_strdup(bs->drv->format_name);
diff --git a/block/qcow2-cache.c b/block/qcow2-cache.c
index 75746a7f43..c48ffebd8f 100644
--- a/block/qcow2-cache.c
+++ b/block/qcow2-cache.c
@@ -62,6 +62,18 @@ static inline int qcow2_cache_get_table_idx(BlockDriverState *bs,
return idx;
}
+static inline const char *qcow2_cache_get_name(BDRVQcow2State *s, Qcow2Cache *c)
+{
+ if (c == s->refcount_block_cache) {
+ return "refcount block";
+ } else if (c == s->l2_table_cache) {
+ return "L2 table";
+ } else {
+ /* Do not abort, because this is not critical */
+ return "unknown";
+ }
+}
+
static void qcow2_cache_table_release(BlockDriverState *bs, Qcow2Cache *c,
int i, int num_tables)
{
@@ -73,7 +85,7 @@ static void qcow2_cache_table_release(BlockDriverState *bs, Qcow2Cache *c,
size_t mem_size = (size_t) s->cluster_size * num_tables;
size_t offset = QEMU_ALIGN_UP((uintptr_t) t, align) - (uintptr_t) t;
size_t length = QEMU_ALIGN_DOWN(mem_size - offset, align);
- if (length > 0) {
+ if (mem_size > offset && length > 0) {
madvise((uint8_t *) t + offset, length, MADV_DONTNEED);
}
#endif
@@ -314,9 +326,18 @@ static int qcow2_cache_do_get(BlockDriverState *bs, Qcow2Cache *c,
uint64_t min_lru_counter = UINT64_MAX;
int min_lru_index = -1;
+ assert(offset != 0);
+
trace_qcow2_cache_get(qemu_coroutine_self(), c == s->l2_table_cache,
offset, read_from_disk);
+ if (offset_into_cluster(s, offset)) {
+ qcow2_signal_corruption(bs, true, -1, -1, "Cannot get entry from %s "
+ "cache: Offset %#" PRIx64 " is unaligned",
+ qcow2_cache_get_name(s, c), offset);
+ return -EIO;
+ }
+
/* Check if the table is already cached */
i = lookup_index = (offset / s->cluster_size * 4) % c->size;
do {
diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
index 2e072ed155..a3fec27bf9 100644
--- a/block/qcow2-cluster.c
+++ b/block/qcow2-cluster.c
@@ -1308,10 +1308,21 @@ static int handle_alloc(BlockDriverState *bs, uint64_t guest_offset,
(!*host_offset ||
start_of_cluster(s, *host_offset) == (entry & L2E_OFFSET_MASK)))
{
+ int preallocated_nb_clusters;
+
+ if (offset_into_cluster(s, entry & L2E_OFFSET_MASK)) {
+ qcow2_signal_corruption(bs, true, -1, -1, "Preallocated zero "
+ "cluster offset %#llx unaligned (guest "
+ "offset: %#" PRIx64 ")",
+ entry & L2E_OFFSET_MASK, guest_offset);
+ ret = -EIO;
+ goto fail;
+ }
+
/* Try to reuse preallocated zero clusters; contiguous normal clusters
* would be fine, too, but count_cow_clusters() above has limited
* nb_clusters already to a range of COW clusters */
- int preallocated_nb_clusters =
+ preallocated_nb_clusters =
count_contiguous_clusters(nb_clusters, s->cluster_size,
&l2_table[l2_index], QCOW_OFLAG_COPIED);
assert(preallocated_nb_clusters > 0);
diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c
index 60b8eef3e8..3de1ab51ba 100644
--- a/block/qcow2-refcount.c
+++ b/block/qcow2-refcount.c
@@ -3077,16 +3077,40 @@ done:
return ret;
}
+static int64_t get_refblock_offset(BlockDriverState *bs, uint64_t offset)
+{
+ BDRVQcow2State *s = bs->opaque;
+ uint32_t index = offset_to_reftable_index(s, offset);
+ int64_t covering_refblock_offset = 0;
+
+ if (index < s->refcount_table_size) {
+ covering_refblock_offset = s->refcount_table[index] & REFT_OFFSET_MASK;
+ }
+ if (!covering_refblock_offset) {
+ qcow2_signal_corruption(bs, true, -1, -1, "Refblock at %#" PRIx64 " is "
+ "not covered by the refcount structures",
+ offset);
+ return -EIO;
+ }
+
+ return covering_refblock_offset;
+}
+
static int qcow2_discard_refcount_block(BlockDriverState *bs,
uint64_t discard_block_offs)
{
BDRVQcow2State *s = bs->opaque;
- uint64_t refblock_offs = get_refblock_offset(s, discard_block_offs);
+ int64_t refblock_offs;
uint64_t cluster_index = discard_block_offs >> s->cluster_bits;
uint32_t block_index = cluster_index & (s->refcount_block_size - 1);
void *refblock;
int ret;
+ refblock_offs = get_refblock_offset(bs, discard_block_offs);
+ if (refblock_offs < 0) {
+ return refblock_offs;
+ }
+
assert(discard_block_offs != 0);
ret = qcow2_cache_get(bs, s->refcount_block_cache, refblock_offs,
diff --git a/block/qcow2.c b/block/qcow2.c
index f2731a7cb5..1914a940e5 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -1477,7 +1477,10 @@ static int qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
BdrvCheckResult result = {0};
ret = qcow2_check(bs, &result, BDRV_FIX_ERRORS | BDRV_FIX_LEAKS);
- if (ret < 0) {
+ if (ret < 0 || result.check_errors) {
+ if (ret >= 0) {
+ ret = -EIO;
+ }
error_setg_errno(errp, -ret, "Could not repair dirty image");
goto fail;
}
@@ -3358,6 +3361,10 @@ qcow2_co_pwritev_compressed(BlockDriverState *bs, uint64_t offset,
return bdrv_truncate(bs->file, cluster_offset, PREALLOC_MODE_OFF, NULL);
}
+ if (offset_into_cluster(s, offset)) {
+ return -EINVAL;
+ }
+
buf = qemu_blockalign(bs, s->cluster_size);
if (bytes != s->cluster_size) {
if (bytes > s->cluster_size ||
diff --git a/block/qcow2.h b/block/qcow2.h
index 782a206ecb..6f0ff15dd0 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -527,12 +527,6 @@ uint32_t offset_to_reftable_index(BDRVQcow2State *s, uint64_t offset)
return offset >> (s->refcount_block_bits + s->cluster_bits);
}
-static inline uint64_t get_refblock_offset(BDRVQcow2State *s, uint64_t offset)
-{
- uint32_t index = offset_to_reftable_index(s, offset);
- return s->refcount_table[index] & REFT_OFFSET_MASK;
-}
-
/* qcow2.c functions */
int qcow2_backing_read1(BlockDriverState *bs, QEMUIOVector *qiov,
int64_t sector_num, int nb_sectors);
diff --git a/block/replication.c b/block/replication.c
index 1c95d673ff..e41e293d2b 100644
--- a/block/replication.c
+++ b/block/replication.c
@@ -342,12 +342,24 @@ static void secondary_do_checkpoint(BDRVReplicationState *s, Error **errp)
return;
}
+ if (!s->active_disk->bs->drv) {
+ error_setg(errp, "Active disk %s is ejected",
+ s->active_disk->bs->node_name);
+ return;
+ }
+
ret = s->active_disk->bs->drv->bdrv_make_empty(s->active_disk->bs);
if (ret < 0) {
error_setg(errp, "Cannot make active disk empty");
return;
}
+ if (!s->hidden_disk->bs->drv) {
+ error_setg(errp, "Hidden disk %s is ejected",
+ s->hidden_disk->bs->node_name);
+ return;
+ }
+
ret = s->hidden_disk->bs->drv->bdrv_make_empty(s->hidden_disk->bs);
if (ret < 0) {
error_setg(errp, "Cannot make hidden disk empty");
@@ -511,6 +523,9 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
return;
}
+ /* Must be true, or the bdrv_getlength() calls would have failed */
+ assert(s->active_disk->bs->drv && s->hidden_disk->bs->drv);
+
if (!s->active_disk->bs->drv->bdrv_make_empty ||
!s->hidden_disk->bs->drv->bdrv_make_empty) {
error_setg(errp,
diff --git a/block/snapshot.c b/block/snapshot.c
index 1d5ab5f90f..be0743abac 100644
--- a/block/snapshot.c
+++ b/block/snapshot.c
@@ -417,6 +417,7 @@ bool bdrv_all_can_snapshot(BlockDriverState **first_bad_bs)
}
aio_context_release(ctx);
if (!ok) {
+ bdrv_next_cleanup(&it);
goto fail;
}
}
@@ -444,6 +445,7 @@ int bdrv_all_delete_snapshot(const char *name, BlockDriverState **first_bad_bs,
}
aio_context_release(ctx);
if (ret < 0) {
+ bdrv_next_cleanup(&it);
goto fail;
}
}
@@ -469,6 +471,7 @@ int bdrv_all_goto_snapshot(const char *name, BlockDriverState **first_bad_bs)
}
aio_context_release(ctx);
if (err < 0) {
+ bdrv_next_cleanup(&it);
goto fail;
}
}
@@ -494,6 +497,7 @@ int bdrv_all_find_snapshot(const char *name, BlockDriverState **first_bad_bs)
}
aio_context_release(ctx);
if (err < 0) {
+ bdrv_next_cleanup(&it);
goto fail;
}
}
@@ -525,6 +529,7 @@ int bdrv_all_create_snapshot(QEMUSnapshotInfo *sn,
}
aio_context_release(ctx);
if (err < 0) {
+ bdrv_next_cleanup(&it);
goto fail;
}
}
@@ -548,6 +553,7 @@ BlockDriverState *bdrv_all_find_vmstate_bs(void)
aio_context_release(ctx);
if (found) {
+ bdrv_next_cleanup(&it);
break;
}
}
diff --git a/block/vvfat.c b/block/vvfat.c
index 0841cc42fc..a690595f2c 100644
--- a/block/vvfat.c
+++ b/block/vvfat.c
@@ -2947,7 +2947,7 @@ static int do_commit(BDRVVVFATState* s)
return ret;
}
- if (s->qcow->bs->drv->bdrv_make_empty) {
+ if (s->qcow->bs->drv && s->qcow->bs->drv->bdrv_make_empty) {
s->qcow->bs->drv->bdrv_make_empty(s->qcow->bs);
}
diff --git a/include/block/block.h b/include/block/block.h
index fbc21daf62..c05cac57e5 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -461,6 +461,7 @@ typedef struct BdrvNextIterator {
BlockDriverState *bdrv_first(BdrvNextIterator *it);
BlockDriverState *bdrv_next(BdrvNextIterator *it);
+void bdrv_next_cleanup(BdrvNextIterator *it);
BlockDriverState *bdrv_next_monitor_owned(BlockDriverState *bs);
bool bdrv_is_encrypted(BlockDriverState *bs);
diff --git a/include/qapi/qmp/qbool.h b/include/qapi/qmp/qbool.h
index a41111c309..f77ea86c4e 100644
--- a/include/qapi/qmp/qbool.h
+++ b/include/qapi/qmp/qbool.h
@@ -24,6 +24,7 @@ typedef struct QBool {
QBool *qbool_from_bool(bool value);
bool qbool_get_bool(const QBool *qb);
QBool *qobject_to_qbool(const QObject *obj);
+bool qbool_is_equal(const QObject *x, const QObject *y);
void qbool_destroy_obj(QObject *obj);
#endif /* QBOOL_H */
diff --git a/include/qapi/qmp/qdict.h b/include/qapi/qmp/qdict.h
index 6588c7f0c8..fc218e7be6 100644
--- a/include/qapi/qmp/qdict.h
+++ b/include/qapi/qmp/qdict.h
@@ -15,6 +15,7 @@
#include "qapi/qmp/qobject.h"
#include "qapi/qmp/qlist.h"
+#include "qapi/qmp/qnull.h"
#include "qapi/qmp/qnum.h"
#include "qemu/queue.h"
@@ -42,6 +43,7 @@ void qdict_del(QDict *qdict, const char *key);
int qdict_haskey(const QDict *qdict, const char *key);
QObject *qdict_get(const QDict *qdict, const char *key);
QDict *qobject_to_qdict(const QObject *obj);
+bool qdict_is_equal(const QObject *x, const QObject *y);
void qdict_iter(const QDict *qdict,
void (*iter)(const char *key, QObject *obj, void *opaque),
void *opaque);
diff --git a/include/qapi/qmp/qlist.h b/include/qapi/qmp/qlist.h
index c4b5fdad9b..ec3fcc1a4c 100644
--- a/include/qapi/qmp/qlist.h
+++ b/include/qapi/qmp/qlist.h
@@ -15,6 +15,7 @@
#include "qapi/qmp/qobject.h"
#include "qapi/qmp/qnum.h"
+#include "qapi/qmp/qnull.h"
#include "qemu/queue.h"
typedef struct QListEntry {
@@ -37,6 +38,8 @@ typedef struct QList {
qlist_append(qlist, qbool_from_bool(value))
#define qlist_append_str(qlist, value) \
qlist_append(qlist, qstring_from_str(value))
+#define qlist_append_null(qlist) \
+ qlist_append(qlist, qnull())
#define QLIST_FOREACH_ENTRY(qlist, var) \
for ((var) = ((qlist)->head.tqh_first); \
@@ -58,6 +61,7 @@ QObject *qlist_peek(QList *qlist);
int qlist_empty(const QList *qlist);
size_t qlist_size(const QList *qlist);
QList *qobject_to_qlist(const QObject *obj);
+bool qlist_is_equal(const QObject *x, const QObject *y);
void qlist_destroy_obj(QObject *obj);
static inline const QListEntry *qlist_first(const QList *qlist)
diff --git a/include/qapi/qmp/qnull.h b/include/qapi/qmp/qnull.h
new file mode 100644
index 0000000000..c992ee2ae1
--- /dev/null
+++ b/include/qapi/qmp/qnull.h
@@ -0,0 +1,32 @@
+/*
+ * QNull
+ *
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * Authors:
+ * Markus Armbruster <armbru@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.1
+ * or later. See the COPYING.LIB file in the top-level directory.
+ */
+
+#ifndef QNULL_H
+#define QNULL_H
+
+#include "qapi/qmp/qobject.h"
+
+struct QNull {
+ QObject base;
+};
+
+extern QNull qnull_;
+
+static inline QNull *qnull(void)
+{
+ QINCREF(&qnull_);
+ return &qnull_;
+}
+
+bool qnull_is_equal(const QObject *x, const QObject *y);
+
+#endif /* QNULL_H */
diff --git a/include/qapi/qmp/qnum.h b/include/qapi/qmp/qnum.h
index d6b0791139..c3d86794bb 100644
--- a/include/qapi/qmp/qnum.h
+++ b/include/qapi/qmp/qnum.h
@@ -69,6 +69,7 @@ double qnum_get_double(QNum *qn);
char *qnum_to_string(QNum *qn);
QNum *qobject_to_qnum(const QObject *obj);
+bool qnum_is_equal(const QObject *x, const QObject *y);
void qnum_destroy_obj(QObject *obj);
#endif /* QNUM_H */
diff --git a/include/qapi/qmp/qobject.h b/include/qapi/qmp/qobject.h
index eab29edd12..38ac68845c 100644
--- a/include/qapi/qmp/qobject.h
+++ b/include/qapi/qmp/qobject.h
@@ -68,6 +68,15 @@ static inline void qobject_incref(QObject *obj)
}
/**
+ * qobject_is_equal(): Return whether the two objects are equal.
+ *
+ * Any of the pointers may be NULL; return true if both are. Always
+ * return false if only one is (therefore a QNull object is not
+ * considered equal to a NULL pointer).
+ */
+bool qobject_is_equal(const QObject *x, const QObject *y);
+
+/**
* qobject_destroy(): Free resources used by the object
*/
void qobject_destroy(QObject *obj);
@@ -93,16 +102,4 @@ static inline QType qobject_type(const QObject *obj)
return obj->type;
}
-struct QNull {
- QObject base;
-};
-
-extern QNull qnull_;
-
-static inline QNull *qnull(void)
-{
- QINCREF(&qnull_);
- return &qnull_;
-}
-
#endif /* QOBJECT_H */
diff --git a/include/qapi/qmp/qstring.h b/include/qapi/qmp/qstring.h
index 10076b7c8c..65c05a9be5 100644
--- a/include/qapi/qmp/qstring.h
+++ b/include/qapi/qmp/qstring.h
@@ -31,6 +31,7 @@ void qstring_append_int(QString *qstring, int64_t value);
void qstring_append(QString *qstring, const char *str);
void qstring_append_chr(QString *qstring, int c);
QString *qobject_to_qstring(const QObject *obj);
+bool qstring_is_equal(const QObject *x, const QObject *y);
void qstring_destroy_obj(QObject *obj);
#endif /* QSTRING_H */
diff --git a/include/qapi/qmp/types.h b/include/qapi/qmp/types.h
index a4bc662bfb..749ac44dcb 100644
--- a/include/qapi/qmp/types.h
+++ b/include/qapi/qmp/types.h
@@ -19,5 +19,6 @@
#include "qapi/qmp/qstring.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qlist.h"
+#include "qapi/qmp/qnull.h"
#endif /* QAPI_QMP_TYPES_H */
diff --git a/migration/block.c b/migration/block.c
index 3282809583..7147171bb7 100644
--- a/migration/block.c
+++ b/migration/block.c
@@ -415,6 +415,7 @@ static int init_blk_migration(QEMUFile *f)
sectors = bdrv_nb_sectors(bs);
if (sectors <= 0) {
ret = sectors;
+ bdrv_next_cleanup(&it);
goto out;
}
diff --git a/qapi/qapi-clone-visitor.c b/qapi/qapi-clone-visitor.c
index d8b62792bc..daab6819b4 100644
--- a/qapi/qapi-clone-visitor.c
+++ b/qapi/qapi-clone-visitor.c
@@ -12,6 +12,7 @@
#include "qapi/clone-visitor.h"
#include "qapi/visitor-impl.h"
#include "qapi/error.h"
+#include "qapi/qmp/qnull.h"
struct QapiCloneVisitor {
Visitor visitor;
diff --git a/qapi/string-input-visitor.c b/qapi/string-input-visitor.c
index 67a0a4a58b..b3fdd0827d 100644
--- a/qapi/string-input-visitor.c
+++ b/qapi/string-input-visitor.c
@@ -16,6 +16,7 @@
#include "qapi/string-input-visitor.h"
#include "qapi/visitor-impl.h"
#include "qapi/qmp/qerror.h"
+#include "qapi/qmp/qnull.h"
#include "qemu/option.h"
#include "qemu/queue.h"
#include "qemu/range.h"
diff --git a/qobject/qbool.c b/qobject/qbool.c
index 0606bbd2a3..ac825fc5a2 100644
--- a/qobject/qbool.c
+++ b/qobject/qbool.c
@@ -52,6 +52,14 @@ QBool *qobject_to_qbool(const QObject *obj)
}
/**
+ * qbool_is_equal(): Test whether the two QBools are equal
+ */
+bool qbool_is_equal(const QObject *x, const QObject *y)
+{
+ return qobject_to_qbool(x)->value == qobject_to_qbool(y)->value;
+}
+
+/**
* qbool_destroy_obj(): Free all memory allocated by a
* QBool object
*/
diff --git a/qobject/qdict.c b/qobject/qdict.c
index 576018e531..e8f15f1132 100644
--- a/qobject/qdict.c
+++ b/qobject/qdict.c
@@ -403,6 +403,35 @@ void qdict_del(QDict *qdict, const char *key)
}
/**
+ * qdict_is_equal(): Test whether the two QDicts are equal
+ *
+ * Here, equality means whether they contain the same keys and whether
+ * the respective values are in turn equal (i.e. invoking
+ * qobject_is_equal() on them yields true).
+ */
+bool qdict_is_equal(const QObject *x, const QObject *y)
+{
+ const QDict *dict_x = qobject_to_qdict(x);
+ const QDict *dict_y = qobject_to_qdict(y);
+ const QDictEntry *e;
+
+ if (qdict_size(dict_x) != qdict_size(dict_y)) {
+ return false;
+ }
+
+ for (e = qdict_first(dict_x); e; e = qdict_next(dict_x, e)) {
+ const QObject *obj_x = qdict_entry_value(e);
+ const QObject *obj_y = qdict_get(dict_y, qdict_entry_key(e));
+
+ if (!qobject_is_equal(obj_x, obj_y)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
* qdict_destroy_obj(): Free all the memory allocated by a QDict
*/
void qdict_destroy_obj(QObject *obj)
diff --git a/qobject/qlist.c b/qobject/qlist.c
index 86b60cb88c..3ef57d31d1 100644
--- a/qobject/qlist.c
+++ b/qobject/qlist.c
@@ -140,6 +140,38 @@ QList *qobject_to_qlist(const QObject *obj)
}
/**
+ * qlist_is_equal(): Test whether the two QLists are equal
+ *
+ * In order to be considered equal, the respective two objects at each
+ * index of the two lists have to compare equal (regarding
+ * qobject_is_equal()), and both lists have to have the same number of
+ * elements.
+ * That means both lists have to contain equal objects in equal order.
+ */
+bool qlist_is_equal(const QObject *x, const QObject *y)
+{
+ const QList *list_x = qobject_to_qlist(x);
+ const QList *list_y = qobject_to_qlist(y);
+ const QListEntry *entry_x, *entry_y;
+
+ entry_x = qlist_first(list_x);
+ entry_y = qlist_first(list_y);
+
+ while (entry_x && entry_y) {
+ if (!qobject_is_equal(qlist_entry_obj(entry_x),
+ qlist_entry_obj(entry_y)))
+ {
+ return false;
+ }
+
+ entry_x = qlist_next(entry_x);
+ entry_y = qlist_next(entry_y);
+ }
+
+ return !entry_x && !entry_y;
+}
+
+/**
* qlist_destroy_obj(): Free all the memory allocated by a QList
*/
void qlist_destroy_obj(QObject *obj)
diff --git a/qobject/qnull.c b/qobject/qnull.c
index 69a21d1059..f6f55f11ea 100644
--- a/qobject/qnull.c
+++ b/qobject/qnull.c
@@ -12,7 +12,7 @@
#include "qemu/osdep.h"
#include "qemu-common.h"
-#include "qapi/qmp/qobject.h"
+#include "qapi/qmp/qnull.h"
QNull qnull_ = {
.base = {
@@ -20,3 +20,12 @@ QNull qnull_ = {
.refcnt = 1,
},
};
+
+/**
+ * qnull_is_equal(): Always return true because any two QNull objects
+ * are equal.
+ */
+bool qnull_is_equal(const QObject *x, const QObject *y)
+{
+ return true;
+}
diff --git a/qobject/qnum.c b/qobject/qnum.c
index 476e81c93b..410686a611 100644
--- a/qobject/qnum.c
+++ b/qobject/qnum.c
@@ -213,6 +213,60 @@ QNum *qobject_to_qnum(const QObject *obj)
}
/**
+ * qnum_is_equal(): Test whether the two QNums are equal
+ *
+ * Negative integers are never considered equal to unsigned integers,
+ * but positive integers in the range [0, INT64_MAX] are considered
+ * equal independently of whether the QNum's kind is i64 or u64.
+ *
+ * Doubles are never considered equal to integers.
+ */
+bool qnum_is_equal(const QObject *x, const QObject *y)
+{
+ QNum *num_x = qobject_to_qnum(x);
+ QNum *num_y = qobject_to_qnum(y);
+
+ switch (num_x->kind) {
+ case QNUM_I64:
+ switch (num_y->kind) {
+ case QNUM_I64:
+ /* Comparison in native int64_t type */
+ return num_x->u.i64 == num_y->u.i64;
+ case QNUM_U64:
+ /* Implicit conversion of x to uin64_t, so we have to
+ * check its sign before */
+ return num_x->u.i64 >= 0 && num_x->u.i64 == num_y->u.u64;
+ case QNUM_DOUBLE:
+ return false;
+ }
+ abort();
+ case QNUM_U64:
+ switch (num_y->kind) {
+ case QNUM_I64:
+ return qnum_is_equal(y, x);
+ case QNUM_U64:
+ /* Comparison in native uint64_t type */
+ return num_x->u.u64 == num_y->u.u64;
+ case QNUM_DOUBLE:
+ return false;
+ }
+ abort();
+ case QNUM_DOUBLE:
+ switch (num_y->kind) {
+ case QNUM_I64:
+ case QNUM_U64:
+ return false;
+ case QNUM_DOUBLE:
+ /* Comparison in native double type */
+ return num_x->u.dbl == num_y->u.dbl;
+ }
+ abort();
+ }
+
+ abort();
+}
+
+/**
* qnum_destroy_obj(): Free all memory allocated by a
* QNum object
*/
diff --git a/qobject/qobject.c b/qobject/qobject.c
index b0cafb66f1..b2a536041d 100644
--- a/qobject/qobject.c
+++ b/qobject/qobject.c
@@ -27,3 +27,32 @@ void qobject_destroy(QObject *obj)
assert(QTYPE_QNULL < obj->type && obj->type < QTYPE__MAX);
qdestroy[obj->type](obj);
}
+
+
+static bool (*qis_equal[QTYPE__MAX])(const QObject *, const QObject *) = {
+ [QTYPE_NONE] = NULL, /* No such object exists */
+ [QTYPE_QNULL] = qnull_is_equal,
+ [QTYPE_QNUM] = qnum_is_equal,
+ [QTYPE_QSTRING] = qstring_is_equal,
+ [QTYPE_QDICT] = qdict_is_equal,
+ [QTYPE_QLIST] = qlist_is_equal,
+ [QTYPE_QBOOL] = qbool_is_equal,
+};
+
+bool qobject_is_equal(const QObject *x, const QObject *y)
+{
+ /* We cannot test x == y because an object does not need to be
+ * equal to itself (e.g. NaN floats are not). */
+
+ if (!x && !y) {
+ return true;
+ }
+
+ if (!x || !y || x->type != y->type) {
+ return false;
+ }
+
+ assert(QTYPE_NONE < x->type && x->type < QTYPE__MAX);
+
+ return qis_equal[x->type](x, y);
+}
diff --git a/qobject/qstring.c b/qobject/qstring.c
index 5da7b5f37c..74182a1c02 100644
--- a/qobject/qstring.c
+++ b/qobject/qstring.c
@@ -129,6 +129,15 @@ const char *qstring_get_str(const QString *qstring)
}
/**
+ * qstring_is_equal(): Test whether the two QStrings are equal
+ */
+bool qstring_is_equal(const QObject *x, const QObject *y)
+{
+ return !strcmp(qobject_to_qstring(x)->string,
+ qobject_to_qstring(y)->string);
+}
+
+/**
* qstring_destroy_obj(): Free all memory allocated by a QString
* object
*/
diff --git a/scripts/coccinelle/qobject.cocci b/scripts/coccinelle/qobject.cocci
index 1120eb1a42..47bcafe9a9 100644
--- a/scripts/coccinelle/qobject.cocci
+++ b/scripts/coccinelle/qobject.cocci
@@ -41,4 +41,7 @@ expression Obj, E;
|
- qlist_append(Obj, qstring_from_str(E));
+ qlist_append_str(Obj, E);
+|
+- qlist_append(Obj, qnull());
++ qlist_append_null(Obj);
)
diff --git a/tests/.gitignore b/tests/.gitignore
index 53cb2efaee..74e55d7264 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -8,6 +8,7 @@ check-qjson
check-qlist
check-qlit
check-qnull
+check-qobject
check-qstring
check-qom-interface
check-qom-proplist
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 434a2ce868..c002352134 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -41,6 +41,7 @@ check-unit-y += tests/check-qlist$(EXESUF)
gcov-files-check-qlist-y = qobject/qlist.c
check-unit-y += tests/check-qnull$(EXESUF)
gcov-files-check-qnull-y = qobject/qnull.c
+check-unit-y += tests/check-qobject$(EXESUF)
check-unit-y += tests/check-qjson$(EXESUF)
gcov-files-check-qjson-y = qobject/qjson.c
check-unit-y += tests/check-qlit$(EXESUF)
@@ -546,7 +547,7 @@ GENERATED_FILES += tests/test-qapi-types.h tests/test-qapi-visit.h \
tests/test-qmp-introspect.h
test-obj-y = tests/check-qnum.o tests/check-qstring.o tests/check-qdict.o \
- tests/check-qlist.o tests/check-qnull.o \
+ tests/check-qlist.o tests/check-qnull.o tests/check-qobject.o \
tests/check-qjson.o tests/check-qlit.o \
tests/test-coroutine.o tests/test-string-output-visitor.o \
tests/test-string-input-visitor.o tests/test-qobject-output-visitor.o \
@@ -580,6 +581,7 @@ tests/check-qstring$(EXESUF): tests/check-qstring.o $(test-util-obj-y)
tests/check-qdict$(EXESUF): tests/check-qdict.o $(test-util-obj-y)
tests/check-qlist$(EXESUF): tests/check-qlist.o $(test-util-obj-y)
tests/check-qnull$(EXESUF): tests/check-qnull.o $(test-util-obj-y)
+tests/check-qobject$(EXESUF): tests/check-qobject.o $(test-util-obj-y)
tests/check-qjson$(EXESUF): tests/check-qjson.o $(test-util-obj-y)
tests/check-qlit$(EXESUF): tests/check-qlit.o $(test-util-obj-y)
tests/check-qom-interface$(EXESUF): tests/check-qom-interface.o $(test-qom-obj-y)
diff --git a/tests/check-qnull.c b/tests/check-qnull.c
index 5c6eb0adc8..afa4400da1 100644
--- a/tests/check-qnull.c
+++ b/tests/check-qnull.c
@@ -8,7 +8,7 @@
*/
#include "qemu/osdep.h"
-#include "qapi/qmp/qobject.h"
+#include "qapi/qmp/qnull.h"
#include "qemu-common.h"
#include "qapi/qobject-input-visitor.h"
#include "qapi/qobject-output-visitor.h"
diff --git a/tests/check-qobject.c b/tests/check-qobject.c
new file mode 100644
index 0000000000..03e9175113
--- /dev/null
+++ b/tests/check-qobject.c
@@ -0,0 +1,328 @@
+/*
+ * Generic QObject unit-tests.
+ *
+ * Copyright (C) 2017 Red Hat Inc.
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ */
+#include "qemu/osdep.h"
+
+#include "qapi/qmp/types.h"
+#include "qemu-common.h"
+
+#include <math.h>
+
+/* Marks the end of the test_equality() argument list.
+ * We cannot use NULL there because that is a valid argument. */
+static QObject test_equality_end_of_arguments;
+
+/**
+ * Test whether all variadic QObject *arguments are equal (@expected
+ * is true) or whether they are all not equal (@expected is false).
+ * Every QObject is tested to be equal to itself (to test
+ * reflexivity), all tests are done both ways (to test symmetry), and
+ * transitivity is not assumed but checked (each object is compared to
+ * every other one).
+ *
+ * Note that qobject_is_equal() is not really an equivalence relation,
+ * so this function may not be used for all objects (reflexivity is
+ * not guaranteed, e.g. in the case of a QNum containing NaN).
+ *
+ * The @_ argument is required because a boolean may not be the last
+ * argument before a variadic argument list (C11 7.16.1.4 para. 4).
+ */
+static void do_test_equality(bool expected, int _, ...)
+{
+ va_list ap_count, ap_extract;
+ QObject **args;
+ int arg_count = 0;
+ int i, j;
+
+ va_start(ap_count, _);
+ va_copy(ap_extract, ap_count);
+ while (va_arg(ap_count, QObject *) != &test_equality_end_of_arguments) {
+ arg_count++;
+ }
+ va_end(ap_count);
+
+ args = g_new(QObject *, arg_count);
+ for (i = 0; i < arg_count; i++) {
+ args[i] = va_arg(ap_extract, QObject *);
+ }
+ va_end(ap_extract);
+
+ for (i = 0; i < arg_count; i++) {
+ g_assert(qobject_is_equal(args[i], args[i]) == true);
+
+ for (j = i + 1; j < arg_count; j++) {
+ g_assert(qobject_is_equal(args[i], args[j]) == expected);
+ }
+ }
+}
+
+#define check_equal(...) \
+ do_test_equality(true, 0, __VA_ARGS__, &test_equality_end_of_arguments)
+#define check_unequal(...) \
+ do_test_equality(false, 0, __VA_ARGS__, &test_equality_end_of_arguments)
+
+static void do_free_all(int _, ...)
+{
+ va_list ap;
+ QObject *obj;
+
+ va_start(ap, _);
+ while ((obj = va_arg(ap, QObject *)) != NULL) {
+ qobject_decref(obj);
+ }
+ va_end(ap);
+}
+
+#define free_all(...) \
+ do_free_all(0, __VA_ARGS__, NULL)
+
+static void qobject_is_equal_null_test(void)
+{
+ check_unequal(qnull(), NULL);
+}
+
+static void qobject_is_equal_num_test(void)
+{
+ QNum *u0, *i0, *d0, *dnan, *um42, *im42, *dm42;
+
+ u0 = qnum_from_uint(0u);
+ i0 = qnum_from_int(0);
+ d0 = qnum_from_double(0.0);
+ dnan = qnum_from_double(NAN);
+ um42 = qnum_from_uint((uint64_t)-42);
+ im42 = qnum_from_int(-42);
+ dm42 = qnum_from_double(-42.0);
+
+ /* Integers representing a mathematically equal number should
+ * compare equal */
+ check_equal(u0, i0);
+ /* Doubles, however, are always unequal to integers */
+ check_unequal(u0, d0);
+ check_unequal(i0, d0);
+
+ /* Do not assume any object is equal to itself -- note however
+ * that NaN cannot occur in a JSON object anyway. */
+ g_assert(qobject_is_equal(QOBJECT(dnan), QOBJECT(dnan)) == false);
+
+ /* No unsigned overflow */
+ check_unequal(um42, im42);
+ check_unequal(um42, dm42);
+ check_unequal(im42, dm42);
+
+ free_all(u0, i0, d0, dnan, um42, im42, dm42);
+}
+
+static void qobject_is_equal_bool_test(void)
+{
+ QBool *btrue_0, *btrue_1, *bfalse_0, *bfalse_1;
+
+ btrue_0 = qbool_from_bool(true);
+ btrue_1 = qbool_from_bool(true);
+ bfalse_0 = qbool_from_bool(false);
+ bfalse_1 = qbool_from_bool(false);
+
+ check_equal(btrue_0, btrue_1);
+ check_equal(bfalse_0, bfalse_1);
+ check_unequal(btrue_0, bfalse_0);
+
+ free_all(btrue_0, btrue_1, bfalse_0, bfalse_1);
+}
+
+static void qobject_is_equal_string_test(void)
+{
+ QString *str_base, *str_whitespace_0, *str_whitespace_1, *str_whitespace_2;
+ QString *str_whitespace_3, *str_case, *str_built;
+
+ str_base = qstring_from_str("foo");
+ str_whitespace_0 = qstring_from_str(" foo");
+ str_whitespace_1 = qstring_from_str("foo ");
+ str_whitespace_2 = qstring_from_str("foo\b");
+ str_whitespace_3 = qstring_from_str("fooo\b");
+ str_case = qstring_from_str("Foo");
+
+ /* Should yield "foo" */
+ str_built = qstring_from_substr("form", 0, 1);
+ qstring_append_chr(str_built, 'o');
+
+ check_unequal(str_base, str_whitespace_0, str_whitespace_1,
+ str_whitespace_2, str_whitespace_3, str_case);
+
+ check_equal(str_base, str_built);
+
+ free_all(str_base, str_whitespace_0, str_whitespace_1, str_whitespace_2,
+ str_whitespace_3, str_case, str_built);
+}
+
+static void qobject_is_equal_list_test(void)
+{
+ QList *list_0, *list_1, *list_cloned;
+ QList *list_reordered, *list_longer, *list_shorter;
+
+ list_0 = qlist_new();
+ list_1 = qlist_new();
+ list_reordered = qlist_new();
+ list_longer = qlist_new();
+ list_shorter = qlist_new();
+
+ qlist_append_int(list_0, 1);
+ qlist_append_int(list_0, 2);
+ qlist_append_int(list_0, 3);
+
+ qlist_append_int(list_1, 1);
+ qlist_append_int(list_1, 2);
+ qlist_append_int(list_1, 3);
+
+ qlist_append_int(list_reordered, 1);
+ qlist_append_int(list_reordered, 3);
+ qlist_append_int(list_reordered, 2);
+
+ qlist_append_int(list_longer, 1);
+ qlist_append_int(list_longer, 2);
+ qlist_append_int(list_longer, 3);
+ qlist_append_null(list_longer);
+
+ qlist_append_int(list_shorter, 1);
+ qlist_append_int(list_shorter, 2);
+
+ list_cloned = qlist_copy(list_0);
+
+ check_equal(list_0, list_1, list_cloned);
+ check_unequal(list_0, list_reordered, list_longer, list_shorter);
+
+ /* With a NaN in it, the list should no longer compare equal to
+ * itself */
+ qlist_append(list_0, qnum_from_double(NAN));
+ g_assert(qobject_is_equal(QOBJECT(list_0), QOBJECT(list_0)) == false);
+
+ free_all(list_0, list_1, list_cloned, list_reordered, list_longer,
+ list_shorter);
+}
+
+static void qobject_is_equal_dict_test(void)
+{
+ Error *local_err = NULL;
+ QDict *dict_0, *dict_1, *dict_cloned;
+ QDict *dict_different_key, *dict_different_value, *dict_different_null_key;
+ QDict *dict_longer, *dict_shorter, *dict_nested;
+ QDict *dict_crumpled;
+
+ dict_0 = qdict_new();
+ dict_1 = qdict_new();
+ dict_different_key = qdict_new();
+ dict_different_value = qdict_new();
+ dict_different_null_key = qdict_new();
+ dict_longer = qdict_new();
+ dict_shorter = qdict_new();
+ dict_nested = qdict_new();
+
+ qdict_put_int(dict_0, "f.o", 1);
+ qdict_put_int(dict_0, "bar", 2);
+ qdict_put_int(dict_0, "baz", 3);
+ qdict_put_null(dict_0, "null");
+
+ qdict_put_int(dict_1, "f.o", 1);
+ qdict_put_int(dict_1, "bar", 2);
+ qdict_put_int(dict_1, "baz", 3);
+ qdict_put_null(dict_1, "null");
+
+ qdict_put_int(dict_different_key, "F.o", 1);
+ qdict_put_int(dict_different_key, "bar", 2);
+ qdict_put_int(dict_different_key, "baz", 3);
+ qdict_put_null(dict_different_key, "null");
+
+ qdict_put_int(dict_different_value, "f.o", 42);
+ qdict_put_int(dict_different_value, "bar", 2);
+ qdict_put_int(dict_different_value, "baz", 3);
+ qdict_put_null(dict_different_value, "null");
+
+ qdict_put_int(dict_different_null_key, "f.o", 1);
+ qdict_put_int(dict_different_null_key, "bar", 2);
+ qdict_put_int(dict_different_null_key, "baz", 3);
+ qdict_put_null(dict_different_null_key, "none");
+
+ qdict_put_int(dict_longer, "f.o", 1);
+ qdict_put_int(dict_longer, "bar", 2);
+ qdict_put_int(dict_longer, "baz", 3);
+ qdict_put_int(dict_longer, "xyz", 4);
+ qdict_put_null(dict_longer, "null");
+
+ qdict_put_int(dict_shorter, "f.o", 1);
+ qdict_put_int(dict_shorter, "bar", 2);
+ qdict_put_int(dict_shorter, "baz", 3);
+
+ qdict_put(dict_nested, "f", qdict_new());
+ qdict_put_int(qdict_get_qdict(dict_nested, "f"), "o", 1);
+ qdict_put_int(dict_nested, "bar", 2);
+ qdict_put_int(dict_nested, "baz", 3);
+ qdict_put_null(dict_nested, "null");
+
+ dict_cloned = qdict_clone_shallow(dict_0);
+
+ check_equal(dict_0, dict_1, dict_cloned);
+ check_unequal(dict_0, dict_different_key, dict_different_value,
+ dict_different_null_key, dict_longer, dict_shorter,
+ dict_nested);
+
+ dict_crumpled = qobject_to_qdict(qdict_crumple(dict_1, &local_err));
+ g_assert(!local_err);
+ check_equal(dict_crumpled, dict_nested);
+
+ qdict_flatten(dict_nested);
+ check_equal(dict_0, dict_nested);
+
+ /* Containing an NaN value will make this dict compare unequal to
+ * itself */
+ qdict_put(dict_0, "NaN", qnum_from_double(NAN));
+ g_assert(qobject_is_equal(QOBJECT(dict_0), QOBJECT(dict_0)) == false);
+
+ free_all(dict_0, dict_1, dict_cloned, dict_different_key,
+ dict_different_value, dict_different_null_key, dict_longer,
+ dict_shorter, dict_nested, dict_crumpled);
+}
+
+static void qobject_is_equal_conversion_test(void)
+{
+ QNum *u0, *i0, *d0;
+ QString *s0, *s_empty;
+ QBool *bfalse;
+
+ u0 = qnum_from_uint(0u);
+ i0 = qnum_from_int(0);
+ d0 = qnum_from_double(0.0);
+ s0 = qstring_from_str("0");
+ s_empty = qstring_new();
+ bfalse = qbool_from_bool(false);
+
+ /* No automatic type conversion */
+ check_unequal(u0, s0, s_empty, bfalse, qnull(), NULL);
+ check_unequal(i0, s0, s_empty, bfalse, qnull(), NULL);
+ check_unequal(d0, s0, s_empty, bfalse, qnull(), NULL);
+
+ free_all(u0, i0, d0, s0, s_empty, bfalse);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ g_test_add_func("/public/qobject_is_equal_null",
+ qobject_is_equal_null_test);
+ g_test_add_func("/public/qobject_is_equal_num", qobject_is_equal_num_test);
+ g_test_add_func("/public/qobject_is_equal_bool",
+ qobject_is_equal_bool_test);
+ g_test_add_func("/public/qobject_is_equal_string",
+ qobject_is_equal_string_test);
+ g_test_add_func("/public/qobject_is_equal_list",
+ qobject_is_equal_list_test);
+ g_test_add_func("/public/qobject_is_equal_dict",
+ qobject_is_equal_dict_test);
+ g_test_add_func("/public/qobject_is_equal_conversion",
+ qobject_is_equal_conversion_test);
+
+ return g_test_run();
+}
diff --git a/tests/qemu-iotests/020 b/tests/qemu-iotests/020
index 7a111100ec..cefe3a830e 100755
--- a/tests/qemu-iotests/020
+++ b/tests/qemu-iotests/020
@@ -110,6 +110,33 @@ for offset in $TEST_OFFSETS; do
io_zero readv $(( offset + 64 * 1024 + 65536 * 4 )) 65536 65536 1
done
_check_test_img
+_cleanup
+TEST_IMG=$TEST_IMG_SAVE
+
+echo
+echo 'Testing failing commit'
+echo
+
+# Create an image with a null backing file to which committing will fail (with
+# ENOSPC so we can distinguish the result from some generic EIO which may be
+# generated anywhere in the block layer)
+_make_test_img -b "json:{'driver': 'raw',
+ 'file': {
+ 'driver': 'blkdebug',
+ 'inject-error': [{
+ 'event': 'write_aio',
+ 'errno': 28,
+ 'once': true
+ }],
+ 'image': {
+ 'driver': 'null-co'
+ }}}"
+
+# Just write anything so committing will not be a no-op
+$QEMU_IO -c 'writev 0 64k' "$TEST_IMG" | _filter_qemu_io
+
+$QEMU_IMG commit "$TEST_IMG"
+_cleanup_test_img
# success, all done
echo "*** done"
diff --git a/tests/qemu-iotests/020.out b/tests/qemu-iotests/020.out
index 42f6c1b151..165b70aa49 100644
--- a/tests/qemu-iotests/020.out
+++ b/tests/qemu-iotests/020.out
@@ -1075,4 +1075,21 @@ read 65536/65536 bytes at offset 4295098368
read 65536/65536 bytes at offset 4295294976
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
No errors were found on the image.
+
+Testing failing commit
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824 backing_file=json:{'driver': 'raw',,
+ 'file': {
+ 'driver': 'blkdebug',,
+ 'inject-error': [{
+ 'event': 'write_aio',,
+ 'errno': 28,,
+ 'once': true
+ }],,
+ 'image': {
+ 'driver': 'null-co'
+ }}}
+wrote 65536/65536 bytes at offset 0
+64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+qemu-img: Block job failed: No space left on device
*** done
diff --git a/tests/qemu-iotests/060 b/tests/qemu-iotests/060
index fae08b03bf..1eca09417b 100755
--- a/tests/qemu-iotests/060
+++ b/tests/qemu-iotests/060
@@ -301,6 +301,131 @@ _make_test_img 64M
poke_file "$TEST_IMG" "48" "\x00\x00\x00\x00\x00\x00\x00\x00"
$QEMU_IO -c "write 0 64k" "$TEST_IMG" | _filter_qemu_io
+echo
+echo "=== Testing dirty corrupt image ==="
+echo
+
+_make_test_img 64M
+
+# Let the refblock appear unaligned
+poke_file "$TEST_IMG" "$rt_offset" "\x00\x00\x00\x00\xff\xff\x2a\x00"
+# Mark the image dirty, thus forcing an automatic check when opening it
+poke_file "$TEST_IMG" 72 "\x00\x00\x00\x00\x00\x00\x00\x01"
+# Open the image (qemu should refuse to do so)
+$QEMU_IO -c close "$TEST_IMG" 2>&1 | _filter_testdir | _filter_imgfmt
+
+echo '--- Repairing ---'
+
+# The actual repair should have happened (because of the dirty bit),
+# but some cleanup may have failed (like freeing the old reftable)
+# because the image was already marked corrupt by that point
+_check_test_img -r all
+
+echo
+echo "=== Writing to an unaligned preallocated zero cluster ==="
+echo
+
+_make_test_img 64M
+
+# Allocate the L2 table
+$QEMU_IO -c "write 0 64k" -c "discard 0 64k" "$TEST_IMG" | _filter_qemu_io
+# Pretend there is a preallocated zero cluster somewhere inside the
+# image header
+poke_file "$TEST_IMG" "$l2_offset" "\x80\x00\x00\x00\x00\x00\x2a\x01"
+# Let's write to it!
+$QEMU_IO -c "write 0 64k" "$TEST_IMG" | _filter_qemu_io
+
+# Can't repair this yet (TODO: We can just deallocate the cluster)
+
+echo
+echo '=== Discarding with an unaligned refblock ==='
+echo
+
+_make_test_img 64M
+
+$QEMU_IO -c "write 0 128k" "$TEST_IMG" | _filter_qemu_io
+# Make our refblock unaligned
+poke_file "$TEST_IMG" "$(($rt_offset))" "\x00\x00\x00\x00\x00\x00\x2a\x00"
+# Now try to discard something that will be submitted as two requests
+# (main part + tail)
+$QEMU_IO -c "discard 0 65537" "$TEST_IMG"
+
+echo '--- Repairing ---'
+# Fails the first repair because the corruption prevents the check
+# function from double-checking
+# (Using -q for the first invocation, because otherwise the
+# double-check error message appears above the summary for some
+# reason -- so let's just hide the summary)
+_check_test_img -q -r all
+_check_test_img -r all
+
+echo
+echo "=== Discarding an out-of-bounds refblock ==="
+echo
+
+_make_test_img 64M
+
+# Pretend there's a refblock really up high
+poke_file "$TEST_IMG" "$(($rt_offset+8))" "\x00\xff\xff\xff\x00\x00\x00\x00"
+# Let's try to shrink the qcow2 image so that the block driver tries
+# to discard that refblock (and see what happens!)
+$QEMU_IMG resize --shrink "$TEST_IMG" 32M
+
+echo '--- Checking and retrying ---'
+# Image should not be resized
+_img_info | grep 'virtual size'
+# But it should pass this check, because the "partial" resize has
+# already overwritten refblocks past the end
+_check_test_img -r all
+# So let's try again
+$QEMU_IMG resize --shrink "$TEST_IMG" 32M
+_img_info | grep 'virtual size'
+
+echo
+echo "=== Discarding a non-covered in-bounds refblock ==="
+echo
+
+IMGOPTS='refcount_bits=1' _make_test_img 64M
+
+# Pretend there's a refblock somewhere where there is no refblock to
+# cover it (but the covering refblock has a valid index in the
+# reftable)
+# Every refblock covers 65536 * 8 * 65536 = 32 GB, so we have to point
+# to 0x10_0000_0000 (64G) to point to the third refblock
+poke_file "$TEST_IMG" "$(($rt_offset+8))" "\x00\x00\x00\x10\x00\x00\x00\x00"
+$QEMU_IMG resize --shrink "$TEST_IMG" 32M
+
+echo '--- Checking and retrying ---'
+# Image should not be resized
+_img_info | grep 'virtual size'
+# But it should pass this check, because the "partial" resize has
+# already overwritten refblocks past the end
+_check_test_img -r all
+# So let's try again
+$QEMU_IMG resize --shrink "$TEST_IMG" 32M
+_img_info | grep 'virtual size'
+
+echo
+echo "=== Discarding a refblock covered by an unaligned refblock ==="
+echo
+
+IMGOPTS='refcount_bits=1' _make_test_img 64M
+
+# Same as above
+poke_file "$TEST_IMG" "$(($rt_offset+8))" "\x00\x00\x00\x10\x00\x00\x00\x00"
+# But now we actually "create" an unaligned third refblock
+poke_file "$TEST_IMG" "$(($rt_offset+16))" "\x00\x00\x00\x00\x00\x00\x02\x00"
+$QEMU_IMG resize --shrink "$TEST_IMG" 32M
+
+echo '--- Repairing ---'
+# Fails the first repair because the corruption prevents the check
+# function from double-checking
+# (Using -q for the first invocation, because otherwise the
+# double-check error message appears above the summary for some
+# reason -- so let's just hide the summary)
+_check_test_img -q -r all
+_check_test_img -r all
+
# success, all done
echo "*** done"
rm -f $seq.full
diff --git a/tests/qemu-iotests/060.out b/tests/qemu-iotests/060.out
index 62c22701b8..56f5eb15d8 100644
--- a/tests/qemu-iotests/060.out
+++ b/tests/qemu-iotests/060.out
@@ -284,4 +284,119 @@ No errors were found on the image.
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
qcow2: Marking image as corrupt: Preventing invalid allocation of L2 table at offset 0; further corruption events will be suppressed
write failed: Input/output error
+
+=== Testing dirty corrupt image ===
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
+ERROR refcount block 0 is not cluster aligned; refcount table entry corrupted
+IMGFMT: Marking image as corrupt: Refblock offset 0xffff2a00 unaligned (reftable index: 0); further corruption events will be suppressed
+Can't get refcount for cluster 0: Input/output error
+Can't get refcount for cluster 1: Input/output error
+Can't get refcount for cluster 2: Input/output error
+Can't get refcount for cluster 3: Input/output error
+Rebuilding refcount structure
+Repairing cluster 1 refcount=1 reference=0
+can't open device TEST_DIR/t.IMGFMT: Could not repair dirty image: Input/output error
+--- Repairing ---
+Leaked cluster 1 refcount=1 reference=0
+Repairing cluster 1 refcount=1 reference=0
+The following inconsistencies were found and repaired:
+
+ 1 leaked clusters
+ 0 corruptions
+
+Double checking the fixed image now...
+No errors were found on the image.
+
+=== Writing to an unaligned preallocated zero cluster ===
+
+Formatting 'TEST_DIR/t.IMGFMT', 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)
+discard 65536/65536 bytes at offset 0
+64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+qcow2: Marking image as corrupt: Preallocated zero cluster offset 0x2a00 unaligned (guest offset: 0); further corruption events will be suppressed
+write failed: Input/output error
+
+=== Discarding with an unaligned refblock ===
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
+wrote 131072/131072 bytes at offset 0
+128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+qcow2: Marking image as corrupt: Refblock offset 0x2a00 unaligned (reftable index: 0); further corruption events will be suppressed
+qcow2_free_clusters failed: Input/output error
+discard failed: No medium found
+--- Repairing ---
+ERROR refcount block 0 is not cluster aligned; refcount table entry corrupted
+qcow2: Marking image as corrupt: Refblock offset 0x2a00 unaligned (reftable index: 0); further corruption events will be suppressed
+Can't get refcount for cluster 0: Input/output error
+Can't get refcount for cluster 1: Input/output error
+Can't get refcount for cluster 2: Input/output error
+Can't get refcount for cluster 3: Input/output error
+Can't get refcount for cluster 4: Input/output error
+Can't get refcount for cluster 5: Input/output error
+Can't get refcount for cluster 6: Input/output error
+Rebuilding refcount structure
+Repairing cluster 1 refcount=1 reference=0
+qemu-img: Check failed: No medium found
+Leaked cluster 1 refcount=1 reference=0
+Repairing cluster 1 refcount=1 reference=0
+The following inconsistencies were found and repaired:
+
+ 1 leaked clusters
+ 0 corruptions
+
+Double checking the fixed image now...
+No errors were found on the image.
+
+=== Discarding an out-of-bounds refblock ===
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
+qcow2: Marking image as corrupt: Refblock at 0xffffff00000000 is not covered by the refcount structures; further corruption events will be suppressed
+qemu-img: Failed to discard unused refblocks: Input/output error
+--- Checking and retrying ---
+virtual size: 64M (67108864 bytes)
+No errors were found on the image.
+Image resized.
+virtual size: 32M (33554432 bytes)
+
+=== Discarding a non-covered in-bounds refblock ===
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
+qcow2: Marking image as corrupt: Refblock at 0x1000000000 is not covered by the refcount structures; further corruption events will be suppressed
+qemu-img: Failed to discard unused refblocks: Input/output error
+--- Checking and retrying ---
+virtual size: 64M (67108864 bytes)
+No errors were found on the image.
+Image resized.
+virtual size: 32M (33554432 bytes)
+
+=== Discarding a refblock covered by an unaligned refblock ===
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
+qcow2: Marking image as corrupt: Cannot get entry from refcount block cache: Offset 0x200 is unaligned; further corruption events will be suppressed
+qemu-img: Failed to discard unused refblocks: Input/output error
+--- Repairing ---
+Repairing refcount block 1 is outside image
+ERROR refcount block 2 is not cluster aligned; refcount table entry corrupted
+qcow2: Marking image as corrupt: Refblock offset 0x200 unaligned (reftable index: 0x2); further corruption events will be suppressed
+Can't get refcount for cluster 1048576: Input/output error
+Rebuilding refcount structure
+Repairing cluster 1 refcount=1 reference=0
+Repairing cluster 2 refcount=1 reference=0
+Repairing cluster 1048576 refcount=1 reference=0
+qemu-img: Check failed: No medium found
+Leaked cluster 1 refcount=1 reference=0
+Leaked cluster 2 refcount=1 reference=0
+Leaked cluster 1048576 refcount=1 reference=0
+Repairing cluster 1 refcount=1 reference=0
+Repairing cluster 2 refcount=1 reference=0
+Repairing cluster 1048576 refcount=1 reference=0
+The following inconsistencies were found and repaired:
+
+ 3 leaked clusters
+ 0 corruptions
+
+Double checking the fixed image now...
+No errors were found on the image.
*** done
diff --git a/tests/qemu-iotests/087 b/tests/qemu-iotests/087
index 27ab6c5151..2561a14456 100755
--- a/tests/qemu-iotests/087
+++ b/tests/qemu-iotests/087
@@ -102,7 +102,14 @@ echo
echo === aio=native without O_DIRECT ===
echo
-run_qemu <<EOF
+# Skip this test if AIO is not enabled in this build
+function run_qemu_filter_aio()
+{
+ run_qemu "$@" | \
+ sed -e 's/is not supported in this build/it requires cache.direct=on, which was not specified/'
+}
+
+run_qemu_filter_aio <<EOF
{ "execute": "qmp_capabilities" }
{ "execute": "blockdev-add",
"arguments": {
diff --git a/tests/qemu-iotests/133 b/tests/qemu-iotests/133
index 9d35a6a1ca..af6b3e1dd4 100755
--- a/tests/qemu-iotests/133
+++ b/tests/qemu-iotests/133
@@ -83,6 +83,15 @@ $QEMU_IO -c 'reopen -o driver=qcow2' $TEST_IMG
$QEMU_IO -c 'reopen -o file.driver=file' $TEST_IMG
$QEMU_IO -c 'reopen -o backing.driver=qcow2' $TEST_IMG
+echo
+echo "=== Check that reopening works with non-string options ==="
+echo
+
+# Using the json: pseudo-protocol we can create non-string options
+# (Invoke 'info' just so we get some output afterwards)
+IMGOPTSSYNTAX=false $QEMU_IO -f null-co -c 'reopen' -c 'info' \
+ "json:{'driver': 'null-co', 'size': 65536}"
+
# success, all done
echo "*** done"
rm -f $seq.full
diff --git a/tests/qemu-iotests/133.out b/tests/qemu-iotests/133.out
index cc86b94880..f4a85aeb63 100644
--- a/tests/qemu-iotests/133.out
+++ b/tests/qemu-iotests/133.out
@@ -19,4 +19,9 @@ Cannot change the option 'driver'
=== Check that unchanged driver is okay ===
+
+=== Check that reopening works with non-string options ===
+
+format name: null-co
+format name: null-co
*** done