summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--block.c27
-rw-r--r--block/blklogwrites.c5
-rw-r--r--block/block-backend.c59
-rw-r--r--block/io.c8
-rw-r--r--block/mirror.c11
-rw-r--r--block/nbd-client.c1
-rw-r--r--block/nvme.c1
-rw-r--r--block/qcow2-refcount.c3
-rw-r--r--block/qcow2.c1
-rw-r--r--block/qcow2.h10
-rw-r--r--block/qed.c1
-rw-r--r--block/vdi.c57
-rw-r--r--block/vmdk.c532
-rw-r--r--block/vpc.c4
-rw-r--r--hw/acpi/vmgenid.c6
-rw-r--r--hw/scsi/scsi-disk.c59
-rw-r--r--hw/scsi/virtio-scsi.c13
-rw-r--r--include/qemu/units.h73
-rw-r--r--include/qemu/uuid.h2
-rw-r--r--include/sysemu/block-backend.h5
-rw-r--r--qapi/block-core.json71
-rw-r--r--qapi/qapi-schema.json16
-rw-r--r--scripts/qtest.py6
-rw-r--r--tests/Makefile.include2
-rw-r--r--tests/qemu-iotests/141.out4
-rwxr-xr-xtests/qemu-iotests/2296
-rw-r--r--tests/qemu-iotests/229.out1
-rwxr-xr-xtests/qemu-iotests/23456
-rw-r--r--tests/qemu-iotests/234.out10
-rw-r--r--tests/qemu-iotests/236.out56
-rwxr-xr-xtests/qemu-iotests/237237
-rw-r--r--tests/qemu-iotests/237.out348
-rwxr-xr-xtests/qemu-iotests/23953
-rw-r--r--tests/qemu-iotests/239.out4
-rwxr-xr-xtests/qemu-iotests/240129
-rw-r--r--tests/qemu-iotests/240.out54
-rwxr-xr-xtests/qemu-iotests/check7
-rw-r--r--tests/qemu-iotests/common.filter1
-rw-r--r--tests/qemu-iotests/group3
-rw-r--r--tests/qemu-iotests/iotests.py22
-rw-r--r--tests/qemu-iotests/sample_images/simple-dmg.dmg.bz2bin0 -> 3479 bytes
-rw-r--r--tests/test-block-iothread.c372
-rw-r--r--tests/vmgenid-test.c2
-rw-r--r--util/uuid.c10
44 files changed, 1931 insertions, 417 deletions
diff --git a/block.c b/block.c
index e795a7c5de..b67d9b7b65 100644
--- a/block.c
+++ b/block.c
@@ -1438,13 +1438,19 @@ static int bdrv_open_common(BlockDriverState *bs, BlockBackend *file,
     bs->read_only = !(bs->open_flags & BDRV_O_RDWR);
 
     if (use_bdrv_whitelist && !bdrv_is_whitelisted(drv, bs->read_only)) {
-        error_setg(errp,
-                   !bs->read_only && bdrv_is_whitelisted(drv, true)
-                        ? "Driver '%s' can only be used for read-only devices"
-                        : "Driver '%s' is not whitelisted",
-                   drv->format_name);
-        ret = -ENOTSUP;
-        goto fail_opts;
+        if (!bs->read_only && bdrv_is_whitelisted(drv, true)) {
+            ret = bdrv_apply_auto_read_only(bs, NULL, NULL);
+        } else {
+            ret = -ENOTSUP;
+        }
+        if (ret < 0) {
+            error_setg(errp,
+                       !bs->read_only && bdrv_is_whitelisted(drv, true)
+                       ? "Driver '%s' can only be used for read-only devices"
+                       : "Driver '%s' is not whitelisted",
+                       drv->format_name);
+            goto fail_opts;
+        }
     }
 
     /* bdrv_new() and bdrv_close() make it so */
@@ -3725,6 +3731,7 @@ static void bdrv_check_co_entry(void *opaque)
 {
     CheckCo *cco = opaque;
     cco->ret = bdrv_co_check(cco->bs, cco->res, cco->fix);
+    aio_wait_kick();
 }
 
 int bdrv_check(BlockDriverState *bs,
@@ -3743,7 +3750,7 @@ int bdrv_check(BlockDriverState *bs,
         bdrv_check_co_entry(&cco);
     } else {
         co = qemu_coroutine_create(bdrv_check_co_entry, &cco);
-        qemu_coroutine_enter(co);
+        bdrv_coroutine_enter(bs, co);
         BDRV_POLL_WHILE(bs, cco.ret == -EINPROGRESS);
     }
 
@@ -4690,6 +4697,7 @@ static void coroutine_fn bdrv_co_invalidate_cache(BlockDriverState *bs,
         if (parent->role->activate) {
             parent->role->activate(parent, &local_err);
             if (local_err) {
+                bs->open_flags |= BDRV_O_INACTIVE;
                 error_propagate(errp, local_err);
                 return;
             }
@@ -4708,6 +4716,7 @@ static void coroutine_fn bdrv_invalidate_cache_co_entry(void *opaque)
     InvalidateCacheCo *ico = opaque;
     bdrv_co_invalidate_cache(ico->bs, ico->errp);
     ico->done = true;
+    aio_wait_kick();
 }
 
 void bdrv_invalidate_cache(BlockDriverState *bs, Error **errp)
@@ -4724,7 +4733,7 @@ void bdrv_invalidate_cache(BlockDriverState *bs, Error **errp)
         bdrv_invalidate_cache_co_entry(&ico);
     } else {
         co = qemu_coroutine_create(bdrv_invalidate_cache_co_entry, &ico);
-        qemu_coroutine_enter(co);
+        bdrv_coroutine_enter(bs, co);
         BDRV_POLL_WHILE(bs, !ico.done);
     }
 }
diff --git a/block/blklogwrites.c b/block/blklogwrites.c
index ff98cd5533..d2e01bdb1d 100644
--- a/block/blklogwrites.c
+++ b/block/blklogwrites.c
@@ -295,10 +295,9 @@ static void blk_log_writes_refresh_filename(BlockDriverState *bs,
         qdict_put_str(opts, "driver", "blklogwrites");
 
         qobject_ref(bs->file->bs->full_open_options);
-        qdict_put_obj(opts, "file", QOBJECT(bs->file->bs->full_open_options));
+        qdict_put(opts, "file", bs->file->bs->full_open_options);
         qobject_ref(s->log_file->bs->full_open_options);
-        qdict_put_obj(opts, "log",
-                      QOBJECT(s->log_file->bs->full_open_options));
+        qdict_put(opts, "log", s->log_file->bs->full_open_options);
         qdict_put_int(opts, "log-sector-size", s->sectorsize);
 
         bs->full_open_options = opts;
diff --git a/block/block-backend.c b/block/block-backend.c
index cf05abd89d..f6ea824308 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -47,9 +47,7 @@ struct BlockBackend {
     QTAILQ_ENTRY(BlockBackend) monitor_link; /* for monitor_block_backends */
     BlockBackendPublic public;
 
-    void *dev;                  /* attached device model, if any */
-    bool legacy_dev;            /* true if dev is not a DeviceState */
-    /* TODO change to DeviceState when all users are qdevified */
+    DeviceState *dev;           /* attached device model, if any */
     const BlockDevOps *dev_ops;
     void *dev_opaque;
 
@@ -836,7 +834,11 @@ void blk_get_perm(BlockBackend *blk, uint64_t *perm, uint64_t *shared_perm)
     *shared_perm = blk->shared_perm;
 }
 
-static int blk_do_attach_dev(BlockBackend *blk, void *dev)
+/*
+ * Attach device model @dev to @blk.
+ * Return 0 on success, -EBUSY when a device model is attached already.
+ */
+int blk_attach_dev(BlockBackend *blk, DeviceState *dev)
 {
     if (blk->dev) {
         return -EBUSY;
@@ -851,40 +853,16 @@ static int blk_do_attach_dev(BlockBackend *blk, void *dev)
 
     blk_ref(blk);
     blk->dev = dev;
-    blk->legacy_dev = false;
     blk_iostatus_reset(blk);
 
     return 0;
 }
 
 /*
- * Attach device model @dev to @blk.
- * Return 0 on success, -EBUSY when a device model is attached already.
- */
-int blk_attach_dev(BlockBackend *blk, DeviceState *dev)
-{
-    return blk_do_attach_dev(blk, dev);
-}
-
-/*
- * Attach device model @dev to @blk.
- * @blk must not have a device model attached already.
- * TODO qdevified devices don't use this, remove when devices are qdevified
- */
-void blk_attach_dev_legacy(BlockBackend *blk, void *dev)
-{
-    if (blk_do_attach_dev(blk, dev) < 0) {
-        abort();
-    }
-    blk->legacy_dev = true;
-}
-
-/*
  * Detach device model @dev from @blk.
  * @dev must be currently attached to @blk.
  */
-void blk_detach_dev(BlockBackend *blk, void *dev)
-/* TODO change to DeviceState *dev when all users are qdevified */
+void blk_detach_dev(BlockBackend *blk, DeviceState *dev)
 {
     assert(blk->dev == dev);
     blk->dev = NULL;
@@ -898,8 +876,7 @@ void blk_detach_dev(BlockBackend *blk, void *dev)
 /*
  * Return the device model attached to @blk if any, else null.
  */
-void *blk_get_attached_dev(BlockBackend *blk)
-/* TODO change to return DeviceState * when all users are qdevified */
+DeviceState *blk_get_attached_dev(BlockBackend *blk)
 {
     return blk->dev;
 }
@@ -908,10 +885,7 @@ void *blk_get_attached_dev(BlockBackend *blk)
  * device attached to the BlockBackend. */
 char *blk_get_attached_dev_id(BlockBackend *blk)
 {
-    DeviceState *dev;
-
-    assert(!blk->legacy_dev);
-    dev = blk->dev;
+    DeviceState *dev = blk->dev;
 
     if (!dev) {
         return g_strdup("");
@@ -949,11 +923,6 @@ BlockBackend *blk_by_dev(void *dev)
 void blk_set_dev_ops(BlockBackend *blk, const BlockDevOps *ops,
                      void *opaque)
 {
-    /* All drivers that use blk_set_dev_ops() are qdevified and we want to keep
-     * it that way, so we can assume blk->dev, if present, is a DeviceState if
-     * blk->dev_ops is set. Non-device users may use dev_ops without device. */
-    assert(!blk->legacy_dev);
-
     blk->dev_ops = ops;
     blk->dev_opaque = opaque;
 
@@ -979,8 +948,6 @@ void blk_dev_change_media_cb(BlockBackend *blk, bool load, Error **errp)
         bool tray_was_open, tray_is_open;
         Error *local_err = NULL;
 
-        assert(!blk->legacy_dev);
-
         tray_was_open = blk_dev_is_tray_open(blk);
         blk->dev_ops->change_media_cb(blk->dev_opaque, load, &local_err);
         if (local_err) {
@@ -1220,6 +1187,7 @@ static void blk_read_entry(void *opaque)
 
     rwco->ret = blk_co_preadv(rwco->blk, rwco->offset, qiov->size,
                               qiov, rwco->flags);
+    aio_wait_kick();
 }
 
 static void blk_write_entry(void *opaque)
@@ -1229,6 +1197,7 @@ static void blk_write_entry(void *opaque)
 
     rwco->ret = blk_co_pwritev(rwco->blk, rwco->offset, qiov->size,
                                qiov, rwco->flags);
+    aio_wait_kick();
 }
 
 static int blk_prw(BlockBackend *blk, int64_t offset, uint8_t *buf,
@@ -1540,6 +1509,7 @@ static void blk_ioctl_entry(void *opaque)
 
     rwco->ret = blk_co_ioctl(rwco->blk, rwco->offset,
                              qiov->iov[0].iov_base);
+    aio_wait_kick();
 }
 
 int blk_ioctl(BlockBackend *blk, unsigned long int req, void *buf)
@@ -1586,6 +1556,7 @@ static void blk_flush_entry(void *opaque)
 {
     BlkRwCo *rwco = opaque;
     rwco->ret = blk_co_flush(rwco->blk);
+    aio_wait_kick();
 }
 
 int blk_flush(BlockBackend *blk)
@@ -1779,9 +1750,6 @@ void blk_eject(BlockBackend *blk, bool eject_flag)
     BlockDriverState *bs = blk_bs(blk);
     char *id;
 
-    /* blk_eject is only called by qdevified devices */
-    assert(!blk->legacy_dev);
-
     if (bs) {
         bdrv_eject(bs, eject_flag);
     }
@@ -2018,6 +1986,7 @@ static void blk_pdiscard_entry(void *opaque)
     QEMUIOVector *qiov = rwco->iobuf;
 
     rwco->ret = blk_co_pdiscard(rwco->blk, rwco->offset, qiov->size);
+    aio_wait_kick();
 }
 
 int blk_pdiscard(BlockBackend *blk, int64_t offset, int bytes)
diff --git a/block/io.c b/block/io.c
index bd9d688f8b..213ca03d8d 100644
--- a/block/io.c
+++ b/block/io.c
@@ -806,6 +806,7 @@ static void coroutine_fn bdrv_rw_co_entry(void *opaque)
                                     rwco->qiov->size, rwco->qiov,
                                     rwco->flags);
     }
+    aio_wait_kick();
 }
 
 /*
@@ -2279,6 +2280,7 @@ static void coroutine_fn bdrv_block_status_above_co_entry(void *opaque)
                                            data->offset, data->bytes,
                                            data->pnum, data->map, data->file);
     data->done = true;
+    aio_wait_kick();
 }
 
 /*
@@ -2438,6 +2440,7 @@ static void coroutine_fn bdrv_co_rw_vmstate_entry(void *opaque)
 {
     BdrvVmstateCo *co = opaque;
     co->ret = bdrv_co_rw_vmstate(co->bs, co->qiov, co->pos, co->is_read);
+    aio_wait_kick();
 }
 
 static inline int
@@ -2559,6 +2562,7 @@ static void coroutine_fn bdrv_flush_co_entry(void *opaque)
     FlushCo *rwco = opaque;
 
     rwco->ret = bdrv_co_flush(rwco->bs);
+    aio_wait_kick();
 }
 
 int coroutine_fn bdrv_co_flush(BlockDriverState *bs)
@@ -2704,6 +2708,7 @@ static void coroutine_fn bdrv_pdiscard_co_entry(void *opaque)
     DiscardCo *rwco = opaque;
 
     rwco->ret = bdrv_co_pdiscard(rwco->child, rwco->offset, rwco->bytes);
+    aio_wait_kick();
 }
 
 int coroutine_fn bdrv_co_pdiscard(BdrvChild *child, int64_t offset, int bytes)
@@ -3217,6 +3222,7 @@ static void coroutine_fn bdrv_truncate_co_entry(void *opaque)
     TruncateCo *tco = opaque;
     tco->ret = bdrv_co_truncate(tco->child, tco->offset, tco->prealloc,
                                 tco->errp);
+    aio_wait_kick();
 }
 
 int bdrv_truncate(BdrvChild *child, int64_t offset, PreallocMode prealloc,
@@ -3236,7 +3242,7 @@ int bdrv_truncate(BdrvChild *child, int64_t offset, PreallocMode prealloc,
         bdrv_truncate_co_entry(&tco);
     } else {
         co = qemu_coroutine_create(bdrv_truncate_co_entry, &tco);
-        qemu_coroutine_enter(co);
+        bdrv_coroutine_enter(child->bs, co);
         BDRV_POLL_WHILE(child->bs, tco.ret == NOT_DONE);
     }
 
diff --git a/block/mirror.c b/block/mirror.c
index 24ede6fdaa..b67b0120f8 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -1612,6 +1612,14 @@ static void mirror_start_job(const char *job_id, BlockDriverState *bs,
         goto fail;
     }
 
+    ret = block_job_add_bdrv(&s->common, "source", bs, 0,
+                             BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE |
+                             BLK_PERM_CONSISTENT_READ,
+                             errp);
+    if (ret < 0) {
+        goto fail;
+    }
+
     /* Required permissions are already taken with blk_new() */
     block_job_add_bdrv(&s->common, "target", target, 0, BLK_PERM_ALL,
                        &error_abort);
@@ -1649,6 +1657,9 @@ fail:
         g_free(s->replaces);
         blk_unref(s->target);
         bs_opaque->job = NULL;
+        if (s->dirty_bitmap) {
+            bdrv_release_dirty_bitmap(bs, s->dirty_bitmap);
+        }
         job_early_fail(&s->common.job);
     }
 
diff --git a/block/nbd-client.c b/block/nbd-client.c
index 813539676d..50a8dadd85 100644
--- a/block/nbd-client.c
+++ b/block/nbd-client.c
@@ -119,6 +119,7 @@ static coroutine_fn void nbd_read_reply_entry(void *opaque)
     s->quit = true;
     nbd_recv_coroutines_wake_all(s);
     s->read_reply_co = NULL;
+    aio_wait_kick();
 }
 
 static int nbd_co_send_request(BlockDriverState *bs,
diff --git a/block/nvme.c b/block/nvme.c
index 982097b5b1..b5952c9b08 100644
--- a/block/nvme.c
+++ b/block/nvme.c
@@ -390,6 +390,7 @@ static void nvme_cmd_sync_cb(void *opaque, int ret)
 {
     int *pret = opaque;
     *pret = ret;
+    aio_wait_kick();
 }
 
 static int nvme_cmd_sync(BlockDriverState *bs, NVMeQueuePair *q,
diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c
index 1c63ac244a..6f13d470d3 100644
--- a/block/qcow2-refcount.c
+++ b/block/qcow2-refcount.c
@@ -368,6 +368,9 @@ static int alloc_refcount_block(BlockDriverState *bs,
         return new_block;
     }
 
+    /* The offset must fit in the offset field of the refcount table entry */
+    assert((new_block & REFT_OFFSET_MASK) == new_block);
+
     /* If we're allocating the block at offset 0 then something is wrong */
     if (new_block == 0) {
         qcow2_signal_corruption(bs, true, -1, -1, "Preventing invalid "
diff --git a/block/qcow2.c b/block/qcow2.c
index 4897abae5e..8c91b92865 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -1671,6 +1671,7 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
         /* From bdrv_co_create.  */
         qcow2_open_entry(&qoc);
     } else {
+        assert(qemu_get_current_aio_context() == qemu_get_aio_context());
         qemu_coroutine_enter(qemu_coroutine_create(qcow2_open_entry, &qoc));
         BDRV_POLL_WHILE(bs, qoc.ret == -EINPROGRESS);
     }
diff --git a/block/qcow2.h b/block/qcow2.h
index 438a1dee9e..32cce9eee2 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -50,11 +50,11 @@
 
 /* 8 MB refcount table is enough for 2 PB images at 64k cluster size
  * (128 GB for 512 byte clusters, 2 EB for 2 MB clusters) */
-#define QCOW_MAX_REFTABLE_SIZE S_8MiB
+#define QCOW_MAX_REFTABLE_SIZE (8 * MiB)
 
 /* 32 MB L1 table is enough for 2 PB images at 64k cluster size
  * (128 GB for 512 byte clusters, 2 EB for 2 MB clusters) */
-#define QCOW_MAX_L1_SIZE S_32MiB
+#define QCOW_MAX_L1_SIZE (32 * MiB)
 
 /* Allow for an average of 1k per snapshot table entry, should be plenty of
  * space for snapshot names and IDs */
@@ -81,15 +81,15 @@
 #define MIN_REFCOUNT_CACHE_SIZE 4 /* clusters */
 
 #ifdef CONFIG_LINUX
-#define DEFAULT_L2_CACHE_MAX_SIZE S_32MiB
+#define DEFAULT_L2_CACHE_MAX_SIZE (32 * MiB)
 #define DEFAULT_CACHE_CLEAN_INTERVAL 600  /* seconds */
 #else
-#define DEFAULT_L2_CACHE_MAX_SIZE S_8MiB
+#define DEFAULT_L2_CACHE_MAX_SIZE (8 * MiB)
 /* Cache clean interval is currently available only on Linux, so must be 0 */
 #define DEFAULT_CACHE_CLEAN_INTERVAL 0
 #endif
 
-#define DEFAULT_CLUSTER_SIZE S_64KiB
+#define DEFAULT_CLUSTER_SIZE 65536
 
 #define QCOW2_OPT_LAZY_REFCOUNTS "lazy-refcounts"
 #define QCOW2_OPT_DISCARD_REQUEST "pass-discard-request"
diff --git a/block/qed.c b/block/qed.c
index 9377c0b7ad..1280870024 100644
--- a/block/qed.c
+++ b/block/qed.c
@@ -559,6 +559,7 @@ static int bdrv_qed_open(BlockDriverState *bs, QDict *options, int flags,
     if (qemu_in_coroutine()) {
         bdrv_qed_open_entry(&qoc);
     } else {
+        assert(qemu_get_current_aio_context() == qemu_get_aio_context());
         qemu_coroutine_enter(qemu_coroutine_create(bdrv_qed_open_entry, &qoc));
         BDRV_POLL_WHILE(bs, qoc.ret == -EINPROGRESS);
     }
diff --git a/block/vdi.c b/block/vdi.c
index 2380daa583..e1c42ad732 100644
--- a/block/vdi.c
+++ b/block/vdi.c
@@ -85,7 +85,8 @@
 #define BLOCK_OPT_STATIC "static"
 
 #define SECTOR_SIZE 512
-#define DEFAULT_CLUSTER_SIZE S_1MiB
+#define DEFAULT_CLUSTER_SIZE 1048576
+/* Note: can't use 1 * MiB, because it's passed to stringify() */
 
 #if defined(CONFIG_VDI_DEBUG)
 #define VDI_DEBUG 1
@@ -203,10 +204,10 @@ static void vdi_header_to_cpu(VdiHeader *header)
     header->block_extra = le32_to_cpu(header->block_extra);
     header->blocks_in_image = le32_to_cpu(header->blocks_in_image);
     header->blocks_allocated = le32_to_cpu(header->blocks_allocated);
-    qemu_uuid_bswap(&header->uuid_image);
-    qemu_uuid_bswap(&header->uuid_last_snap);
-    qemu_uuid_bswap(&header->uuid_link);
-    qemu_uuid_bswap(&header->uuid_parent);
+    header->uuid_image = qemu_uuid_bswap(header->uuid_image);
+    header->uuid_last_snap = qemu_uuid_bswap(header->uuid_last_snap);
+    header->uuid_link = qemu_uuid_bswap(header->uuid_link);
+    header->uuid_parent = qemu_uuid_bswap(header->uuid_parent);
 }
 
 static void vdi_header_to_le(VdiHeader *header)
@@ -227,15 +228,16 @@ static void vdi_header_to_le(VdiHeader *header)
     header->block_extra = cpu_to_le32(header->block_extra);
     header->blocks_in_image = cpu_to_le32(header->blocks_in_image);
     header->blocks_allocated = cpu_to_le32(header->blocks_allocated);
-    qemu_uuid_bswap(&header->uuid_image);
-    qemu_uuid_bswap(&header->uuid_last_snap);
-    qemu_uuid_bswap(&header->uuid_link);
-    qemu_uuid_bswap(&header->uuid_parent);
+    header->uuid_image = qemu_uuid_bswap(header->uuid_image);
+    header->uuid_last_snap = qemu_uuid_bswap(header->uuid_last_snap);
+    header->uuid_link = qemu_uuid_bswap(header->uuid_link);
+    header->uuid_parent = qemu_uuid_bswap(header->uuid_parent);
 }
 
 static void vdi_header_print(VdiHeader *header)
 {
-    char uuid[37];
+    char uuidstr[37];
+    QemuUUID uuid;
     logout("text        %s", header->text);
     logout("signature   0x%08x\n", header->signature);
     logout("header size 0x%04x\n", header->header_size);
@@ -254,14 +256,18 @@ static void vdi_header_print(VdiHeader *header)
     logout("block extra 0x%04x\n", header->block_extra);
     logout("blocks tot. 0x%04x\n", header->blocks_in_image);
     logout("blocks all. 0x%04x\n", header->blocks_allocated);
-    qemu_uuid_unparse(&header->uuid_image, uuid);
-    logout("uuid image  %s\n", uuid);
-    qemu_uuid_unparse(&header->uuid_last_snap, uuid);
-    logout("uuid snap   %s\n", uuid);
-    qemu_uuid_unparse(&header->uuid_link, uuid);
-    logout("uuid link   %s\n", uuid);
-    qemu_uuid_unparse(&header->uuid_parent, uuid);
-    logout("uuid parent %s\n", uuid);
+    uuid = header->uuid_image;
+    qemu_uuid_unparse(&uuid, uuidstr);
+    logout("uuid image  %s\n", uuidstr);
+    uuid = header->uuid_last_snap;
+    qemu_uuid_unparse(&uuid, uuidstr);
+    logout("uuid snap   %s\n", uuidstr);
+    uuid = header->uuid_link;
+    qemu_uuid_unparse(&uuid, uuidstr);
+    logout("uuid link   %s\n", uuidstr);
+    uuid = header->uuid_parent;
+    qemu_uuid_unparse(&uuid, uuidstr);
+    logout("uuid parent %s\n", uuidstr);
 }
 
 static int coroutine_fn vdi_co_check(BlockDriverState *bs, BdrvCheckResult *res,
@@ -368,6 +374,7 @@ static int vdi_open(BlockDriverState *bs, QDict *options, int flags,
     size_t bmap_size;
     int ret;
     Error *local_err = NULL;
+    QemuUUID uuid_link, uuid_parent;
 
     bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
                                false, errp);
@@ -395,6 +402,9 @@ static int vdi_open(BlockDriverState *bs, QDict *options, int flags,
         goto fail;
     }
 
+    uuid_link = header.uuid_link;
+    uuid_parent = header.uuid_parent;
+
     if (header.disk_size % SECTOR_SIZE != 0) {
         /* 'VBoxManage convertfromraw' can create images with odd disk sizes.
            We accept them but round the disk size to the next multiple of
@@ -444,11 +454,11 @@ static int vdi_open(BlockDriverState *bs, QDict *options, int flags,
                    (uint64_t)header.blocks_in_image * header.block_size);
         ret = -ENOTSUP;
         goto fail;
-    } else if (!qemu_uuid_is_null(&header.uuid_link)) {
+    } else if (!qemu_uuid_is_null(&uuid_link)) {
         error_setg(errp, "unsupported VDI image (non-NULL link UUID)");
         ret = -ENOTSUP;
         goto fail;
-    } else if (!qemu_uuid_is_null(&header.uuid_parent)) {
+    } else if (!qemu_uuid_is_null(&uuid_parent)) {
         error_setg(errp, "unsupported VDI image (non-NULL parent UUID)");
         ret = -ENOTSUP;
         goto fail;
@@ -733,6 +743,7 @@ static int coroutine_fn vdi_co_do_create(BlockdevCreateOptions *create_options,
     BlockDriverState *bs_file = NULL;
     BlockBackend *blk = NULL;
     uint32_t *bmap = NULL;
+    QemuUUID uuid;
 
     assert(create_options->driver == BLOCKDEV_DRIVER_VDI);
     vdi_opts = &create_options->u.vdi;
@@ -819,8 +830,10 @@ static int coroutine_fn vdi_co_do_create(BlockdevCreateOptions *create_options,
     if (image_type == VDI_TYPE_STATIC) {
         header.blocks_allocated = blocks;
     }
-    qemu_uuid_generate(&header.uuid_image);
-    qemu_uuid_generate(&header.uuid_last_snap);
+    qemu_uuid_generate(&uuid);
+    header.uuid_image = uuid;
+    qemu_uuid_generate(&uuid);
+    header.uuid_last_snap = uuid;
     /* There is no need to set header.uuid_link or header.uuid_parent here. */
     if (VDI_DEBUG) {
         vdi_header_print(&header);
diff --git a/block/vmdk.c b/block/vmdk.c
index 2c9e86d98f..682ad93aa1 100644
--- a/block/vmdk.c
+++ b/block/vmdk.c
@@ -1741,35 +1741,17 @@ static int coroutine_fn vmdk_co_pwrite_zeroes(BlockDriverState *bs,
     return ret;
 }
 
-static int vmdk_create_extent(const char *filename, int64_t filesize,
-                              bool flat, bool compress, bool zeroed_grain,
-                              QemuOpts *opts, Error **errp)
+static int vmdk_init_extent(BlockBackend *blk,
+                            int64_t filesize, bool flat,
+                            bool compress, bool zeroed_grain,
+                            Error **errp)
 {
     int ret, i;
-    BlockBackend *blk = NULL;
     VMDK4Header header;
-    Error *local_err = NULL;
     uint32_t tmp, magic, grains, gd_sectors, gt_size, gt_count;
     uint32_t *gd_buf = NULL;
     int gd_buf_size;
 
-    ret = bdrv_create_file(filename, opts, &local_err);
-    if (ret < 0) {
-        error_propagate(errp, local_err);
-        goto exit;
-    }
-
-    blk = blk_new_open(filename, NULL, NULL,
-                       BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL,
-                       &local_err);
-    if (blk == NULL) {
-        error_propagate(errp, local_err);
-        ret = -EIO;
-        goto exit;
-    }
-
-    blk_set_allow_write_beyond_eof(blk, true);
-
     if (flat) {
         ret = blk_truncate(blk, filesize, PREALLOC_MODE_OFF, errp);
         goto exit;
@@ -1863,15 +1845,50 @@ static int vmdk_create_extent(const char *filename, int64_t filesize,
                      gd_buf, gd_buf_size, 0);
     if (ret < 0) {
         error_setg(errp, QERR_IO_ERROR);
-        goto exit;
     }
 
     ret = 0;
 exit:
+    g_free(gd_buf);
+    return ret;
+}
+
+static int vmdk_create_extent(const char *filename, int64_t filesize,
+                              bool flat, bool compress, bool zeroed_grain,
+                              BlockBackend **pbb,
+                              QemuOpts *opts, Error **errp)
+{
+    int ret;
+    BlockBackend *blk = NULL;
+    Error *local_err = NULL;
+
+    ret = bdrv_create_file(filename, opts, &local_err);
+    if (ret < 0) {
+        error_propagate(errp, local_err);
+        goto exit;
+    }
+
+    blk = blk_new_open(filename, NULL, NULL,
+                       BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL,
+                       &local_err);
+    if (blk == NULL) {
+        error_propagate(errp, local_err);
+        ret = -EIO;
+        goto exit;
+    }
+
+    blk_set_allow_write_beyond_eof(blk, true);
+
+    ret = vmdk_init_extent(blk, filesize, flat, compress, zeroed_grain, errp);
+exit:
     if (blk) {
-        blk_unref(blk);
+        if (pbb) {
+            *pbb = blk;
+        } else {
+            blk_unref(blk);
+            blk = NULL;
+        }
     }
-    g_free(gd_buf);
     return ret;
 }
 
@@ -1915,33 +1932,57 @@ static int filename_decompose(const char *filename, char *path, char *prefix,
     return VMDK_OK;
 }
 
-static int coroutine_fn vmdk_co_create_opts(const char *filename, QemuOpts *opts,
-                                            Error **errp)
+/*
+ * idx == 0: get or create the descriptor file (also the image file if in a
+ *           non-split format.
+ * idx >= 1: get the n-th extent if in a split subformat
+ */
+typedef BlockBackend *(*vmdk_create_extent_fn)(int64_t size,
+                                               int idx,
+                                               bool flat,
+                                               bool split,
+                                               bool compress,
+                                               bool zeroed_grain,
+                                               void *opaque,
+                                               Error **errp);
+
+static void vmdk_desc_add_extent(GString *desc,
+                                 const char *extent_line_fmt,
+                                 int64_t size, const char *filename)
 {
-    int idx = 0;
-    BlockBackend *new_blk = NULL;
+    char *basename = g_path_get_basename(filename);
+
+    g_string_append_printf(desc, extent_line_fmt,
+                           DIV_ROUND_UP(size, BDRV_SECTOR_SIZE), basename);
+    g_free(basename);
+}
+
+static int coroutine_fn vmdk_co_do_create(int64_t size,
+                                          BlockdevVmdkSubformat subformat,
+                                          BlockdevVmdkAdapterType adapter_type,
+                                          const char *backing_file,
+                                          const char *hw_version,
+                                          bool compat6,
+                                          bool zeroed_grain,
+                                          vmdk_create_extent_fn extent_fn,
+                                          void *opaque,
+                                          Error **errp)
+{
+    int extent_idx;
+    BlockBackend *blk = NULL;
+    BlockBackend *extent_blk;
     Error *local_err = NULL;
     char *desc = NULL;
-    int64_t total_size = 0, filesize;
-    char *adapter_type = NULL;
-    char *backing_file = NULL;
-    char *hw_version = NULL;
-    char *fmt = NULL;
     int ret = 0;
     bool flat, split, compress;
     GString *ext_desc_lines;
-    char *path = g_malloc0(PATH_MAX);
-    char *prefix = g_malloc0(PATH_MAX);
-    char *postfix = g_malloc0(PATH_MAX);
-    char *desc_line = g_malloc0(BUF_SIZE);
-    char *ext_filename = g_malloc0(PATH_MAX);
-    char *desc_filename = g_malloc0(PATH_MAX);
     const int64_t split_size = 0x80000000;  /* VMDK has constant split size */
-    const char *desc_extent_line;
+    int64_t extent_size;
+    int64_t created_size = 0;
+    const char *extent_line_fmt;
     char *parent_desc_line = g_malloc0(BUF_SIZE);
     uint32_t parent_cid = 0xffffffff;
     uint32_t number_heads = 16;
-    bool zeroed_grain = false;
     uint32_t desc_offset = 0, desc_len;
     const char desc_template[] =
         "# Disk DescriptorFile\n"
@@ -1965,71 +2006,35 @@ static int coroutine_fn vmdk_co_create_opts(const char *filename, QemuOpts *opts
 
     ext_desc_lines = g_string_new(NULL);
 
-    if (filename_decompose(filename, path, prefix, postfix, PATH_MAX, errp)) {
-        ret = -EINVAL;
-        goto exit;
-    }
     /* Read out options */
-    total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
-                          BDRV_SECTOR_SIZE);
-    adapter_type = qemu_opt_get_del(opts, BLOCK_OPT_ADAPTER_TYPE);
-    backing_file = qemu_opt_get_del(opts, BLOCK_OPT_BACKING_FILE);
-    hw_version = qemu_opt_get_del(opts, BLOCK_OPT_HWVERSION);
-    if (qemu_opt_get_bool_del(opts, BLOCK_OPT_COMPAT6, false)) {
-        if (strcmp(hw_version, "undefined")) {
+    if (compat6) {
+        if (hw_version) {
             error_setg(errp,
                        "compat6 cannot be enabled with hwversion set");
             ret = -EINVAL;
             goto exit;
         }
-        g_free(hw_version);
-        hw_version = g_strdup("6");
+        hw_version = "6";
     }
-    if (strcmp(hw_version, "undefined") == 0) {
-        g_free(hw_version);
-        hw_version = g_strdup("4");
-    }
-    fmt = qemu_opt_get_del(opts, BLOCK_OPT_SUBFMT);
-    if (qemu_opt_get_bool_del(opts, BLOCK_OPT_ZEROED_GRAIN, false)) {
-        zeroed_grain = true;
+    if (!hw_version) {
+        hw_version = "4";
     }
 
-    if (!adapter_type) {
-        adapter_type = g_strdup("ide");
-    } else if (strcmp(adapter_type, "ide") &&
-               strcmp(adapter_type, "buslogic") &&
-               strcmp(adapter_type, "lsilogic") &&
-               strcmp(adapter_type, "legacyESX")) {
-        error_setg(errp, "Unknown adapter type: '%s'", adapter_type);
-        ret = -EINVAL;
-        goto exit;
-    }
-    if (strcmp(adapter_type, "ide") != 0) {
+    if (adapter_type != BLOCKDEV_VMDK_ADAPTER_TYPE_IDE) {
         /* that's the number of heads with which vmware operates when
            creating, exporting, etc. vmdk files with a non-ide adapter type */
         number_heads = 255;
     }
-    if (!fmt) {
-        /* Default format to monolithicSparse */
-        fmt = g_strdup("monolithicSparse");
-    } else if (strcmp(fmt, "monolithicFlat") &&
-               strcmp(fmt, "monolithicSparse") &&
-               strcmp(fmt, "twoGbMaxExtentSparse") &&
-               strcmp(fmt, "twoGbMaxExtentFlat") &&
-               strcmp(fmt, "streamOptimized")) {
-        error_setg(errp, "Unknown subformat: '%s'", fmt);
-        ret = -EINVAL;
-        goto exit;
-    }
-    split = !(strcmp(fmt, "twoGbMaxExtentFlat") &&
-              strcmp(fmt, "twoGbMaxExtentSparse"));
-    flat = !(strcmp(fmt, "monolithicFlat") &&
-             strcmp(fmt, "twoGbMaxExtentFlat"));
-    compress = !strcmp(fmt, "streamOptimized");
+    split = (subformat == BLOCKDEV_VMDK_SUBFORMAT_TWOGBMAXEXTENTFLAT) ||
+            (subformat == BLOCKDEV_VMDK_SUBFORMAT_TWOGBMAXEXTENTSPARSE);
+    flat = (subformat == BLOCKDEV_VMDK_SUBFORMAT_MONOLITHICFLAT) ||
+           (subformat == BLOCKDEV_VMDK_SUBFORMAT_TWOGBMAXEXTENTFLAT);
+    compress = subformat == BLOCKDEV_VMDK_SUBFORMAT_STREAMOPTIMIZED;
+
     if (flat) {
-        desc_extent_line = "RW %" PRId64 " FLAT \"%s\" 0\n";
+        extent_line_fmt = "RW %" PRId64 " FLAT \"%s\" 0\n";
     } else {
-        desc_extent_line = "RW %" PRId64 " SPARSE \"%s\"\n";
+        extent_line_fmt = "RW %" PRId64 " SPARSE \"%s\"\n";
     }
     if (flat && backing_file) {
         error_setg(errp, "Flat image can't have backing file");
@@ -2041,10 +2046,34 @@ static int coroutine_fn vmdk_co_create_opts(const char *filename, QemuOpts *opts
         ret = -ENOTSUP;
         goto exit;
     }
+
+    /* Create extents */
+    if (split) {
+        extent_size = split_size;
+    } else {
+        extent_size = size;
+    }
+    if (!split && !flat) {
+        created_size = extent_size;
+    } else {
+        created_size = 0;
+    }
+    /* Get the descriptor file BDS */
+    blk = extent_fn(created_size, 0, flat, split, compress, zeroed_grain,
+                    opaque, errp);
+    if (!blk) {
+        ret = -EIO;
+        goto exit;
+    }
+    if (!split && !flat) {
+        vmdk_desc_add_extent(ext_desc_lines, extent_line_fmt, created_size,
+                             blk_bs(blk)->filename);
+    }
+
     if (backing_file) {
-        BlockBackend *blk;
+        BlockBackend *backing;
         char *full_backing = g_new0(char, PATH_MAX);
-        bdrv_get_full_backing_filename_from_filename(filename, backing_file,
+        bdrv_get_full_backing_filename_from_filename(blk_bs(blk)->filename, backing_file,
                                                      full_backing, PATH_MAX,
                                                      &local_err);
         if (local_err) {
@@ -2054,106 +2083,227 @@ static int coroutine_fn vmdk_co_create_opts(const char *filename, QemuOpts *opts
             goto exit;
         }
 
-        blk = blk_new_open(full_backing, NULL, NULL,
-                           BDRV_O_NO_BACKING, errp);
+        backing = blk_new_open(full_backing, NULL, NULL,
+                               BDRV_O_NO_BACKING, errp);
         g_free(full_backing);
-        if (blk == NULL) {
+        if (backing == NULL) {
             ret = -EIO;
             goto exit;
         }
-        if (strcmp(blk_bs(blk)->drv->format_name, "vmdk")) {
-            blk_unref(blk);
+        if (strcmp(blk_bs(backing)->drv->format_name, "vmdk")) {
+            error_setg(errp, "Invalid backing file format: %s. Must be vmdk",
+                       blk_bs(backing)->drv->format_name);
+            blk_unref(backing);
             ret = -EINVAL;
             goto exit;
         }
-        ret = vmdk_read_cid(blk_bs(blk), 0, &parent_cid);
-        blk_unref(blk);
+        ret = vmdk_read_cid(blk_bs(backing), 0, &parent_cid);
+        blk_unref(backing);
         if (ret) {
+            error_setg(errp, "Failed to read parent CID");
             goto exit;
         }
         snprintf(parent_desc_line, BUF_SIZE,
                 "parentFileNameHint=\"%s\"", backing_file);
     }
-
-    /* Create extents */
-    filesize = total_size;
-    while (filesize > 0) {
-        int64_t size = filesize;
-
-        if (split && size > split_size) {
-            size = split_size;
-        }
-        if (split) {
-            snprintf(desc_filename, PATH_MAX, "%s-%c%03d%s",
-                    prefix, flat ? 'f' : 's', ++idx, postfix);
-        } else if (flat) {
-            snprintf(desc_filename, PATH_MAX, "%s-flat%s", prefix, postfix);
-        } else {
-            snprintf(desc_filename, PATH_MAX, "%s%s", prefix, postfix);
-        }
-        snprintf(ext_filename, PATH_MAX, "%s%s", path, desc_filename);
-
-        if (vmdk_create_extent(ext_filename, size,
-                               flat, compress, zeroed_grain, opts, errp)) {
+    extent_idx = 1;
+    while (created_size < size) {
+        int64_t cur_size = MIN(size - created_size, extent_size);
+        extent_blk = extent_fn(cur_size, extent_idx, flat, split, compress,
+                               zeroed_grain, opaque, errp);
+        if (!extent_blk) {
             ret = -EINVAL;
             goto exit;
         }
-        filesize -= size;
+        vmdk_desc_add_extent(ext_desc_lines, extent_line_fmt, cur_size,
+                             blk_bs(extent_blk)->filename);
+        created_size += cur_size;
+        extent_idx++;
+        blk_unref(extent_blk);
+    }
 
-        /* Format description line */
-        snprintf(desc_line, BUF_SIZE,
-                    desc_extent_line, size / BDRV_SECTOR_SIZE, desc_filename);
-        g_string_append(ext_desc_lines, desc_line);
+    /* Check whether we got excess extents */
+    extent_blk = extent_fn(-1, extent_idx, flat, split, compress, zeroed_grain,
+                           opaque, NULL);
+    if (extent_blk) {
+        blk_unref(extent_blk);
+        error_setg(errp, "List of extents contains unused extents");
+        ret = -EINVAL;
+        goto exit;
     }
+
     /* generate descriptor file */
     desc = g_strdup_printf(desc_template,
                            g_random_int(),
                            parent_cid,
-                           fmt,
+                           BlockdevVmdkSubformat_str(subformat),
                            parent_desc_line,
                            ext_desc_lines->str,
                            hw_version,
-                           total_size /
+                           size /
                                (int64_t)(63 * number_heads * BDRV_SECTOR_SIZE),
                            number_heads,
-                           adapter_type);
+                           BlockdevVmdkAdapterType_str(adapter_type));
     desc_len = strlen(desc);
     /* the descriptor offset = 0x200 */
     if (!split && !flat) {
         desc_offset = 0x200;
-    } else {
-        ret = bdrv_create_file(filename, opts, &local_err);
+    }
+
+    ret = blk_pwrite(blk, desc_offset, desc, desc_len, 0);
+    if (ret < 0) {
+        error_setg_errno(errp, -ret, "Could not write description");
+        goto exit;
+    }
+    /* bdrv_pwrite write padding zeros to align to sector, we don't need that
+     * for description file */
+    if (desc_offset == 0) {
+        ret = blk_truncate(blk, desc_len, PREALLOC_MODE_OFF, errp);
         if (ret < 0) {
-            error_propagate(errp, local_err);
             goto exit;
         }
     }
+    ret = 0;
+exit:
+    if (blk) {
+        blk_unref(blk);
+    }
+    g_free(desc);
+    g_free(parent_desc_line);
+    g_string_free(ext_desc_lines, true);
+    return ret;
+}
+
+typedef struct {
+    char *path;
+    char *prefix;
+    char *postfix;
+    QemuOpts *opts;
+} VMDKCreateOptsData;
+
+static BlockBackend *vmdk_co_create_opts_cb(int64_t size, int idx,
+                                            bool flat, bool split, bool compress,
+                                            bool zeroed_grain, void *opaque,
+                                            Error **errp)
+{
+    BlockBackend *blk = NULL;
+    BlockDriverState *bs = NULL;
+    VMDKCreateOptsData *data = opaque;
+    char *ext_filename = NULL;
+    char *rel_filename = NULL;
+
+    /* We're done, don't create excess extents. */
+    if (size == -1) {
+        assert(errp == NULL);
+        return NULL;
+    }
 
-    new_blk = blk_new_open(filename, NULL, NULL,
-                           BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL,
-                           &local_err);
-    if (new_blk == NULL) {
-        error_propagate(errp, local_err);
-        ret = -EIO;
+    if (idx == 0) {
+        rel_filename = g_strdup_printf("%s%s", data->prefix, data->postfix);
+    } else if (split) {
+        rel_filename = g_strdup_printf("%s-%c%03d%s",
+                                       data->prefix,
+                                       flat ? 'f' : 's', idx, data->postfix);
+    } else {
+        assert(idx == 1);
+        rel_filename = g_strdup_printf("%s-flat%s", data->prefix, data->postfix);
+    }
+
+    ext_filename = g_strdup_printf("%s%s", data->path, rel_filename);
+    g_free(rel_filename);
+
+    if (vmdk_create_extent(ext_filename, size,
+                           flat, compress, zeroed_grain, &blk, data->opts,
+                           errp)) {
         goto exit;
     }
+    bdrv_unref(bs);
+exit:
+    g_free(ext_filename);
+    return blk;
+}
 
-    blk_set_allow_write_beyond_eof(new_blk, true);
+static int coroutine_fn vmdk_co_create_opts(const char *filename, QemuOpts *opts,
+                                            Error **errp)
+{
+    Error *local_err = NULL;
+    char *desc = NULL;
+    int64_t total_size = 0;
+    char *adapter_type = NULL;
+    BlockdevVmdkAdapterType adapter_type_enum;
+    char *backing_file = NULL;
+    char *hw_version = NULL;
+    char *fmt = NULL;
+    BlockdevVmdkSubformat subformat;
+    int ret = 0;
+    char *path = g_malloc0(PATH_MAX);
+    char *prefix = g_malloc0(PATH_MAX);
+    char *postfix = g_malloc0(PATH_MAX);
+    char *desc_line = g_malloc0(BUF_SIZE);
+    char *ext_filename = g_malloc0(PATH_MAX);
+    char *desc_filename = g_malloc0(PATH_MAX);
+    char *parent_desc_line = g_malloc0(BUF_SIZE);
+    bool zeroed_grain;
+    bool compat6;
+    VMDKCreateOptsData data;
 
-    ret = blk_pwrite(new_blk, desc_offset, desc, desc_len, 0);
-    if (ret < 0) {
-        error_setg_errno(errp, -ret, "Could not write description");
+    if (filename_decompose(filename, path, prefix, postfix, PATH_MAX, errp)) {
+        ret = -EINVAL;
         goto exit;
     }
-    /* bdrv_pwrite write padding zeros to align to sector, we don't need that
-     * for description file */
-    if (desc_offset == 0) {
-        ret = blk_truncate(new_blk, desc_len, PREALLOC_MODE_OFF, errp);
+    /* Read out options */
+    total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
+                          BDRV_SECTOR_SIZE);
+    adapter_type = qemu_opt_get_del(opts, BLOCK_OPT_ADAPTER_TYPE);
+    backing_file = qemu_opt_get_del(opts, BLOCK_OPT_BACKING_FILE);
+    hw_version = qemu_opt_get_del(opts, BLOCK_OPT_HWVERSION);
+    compat6 = qemu_opt_get_bool_del(opts, BLOCK_OPT_COMPAT6, false);
+    if (strcmp(hw_version, "undefined") == 0) {
+        g_free(hw_version);
+        hw_version = g_strdup("4");
     }
-exit:
-    if (new_blk) {
-        blk_unref(new_blk);
+    fmt = qemu_opt_get_del(opts, BLOCK_OPT_SUBFMT);
+    zeroed_grain = qemu_opt_get_bool_del(opts, BLOCK_OPT_ZEROED_GRAIN, false);
+
+    if (adapter_type) {
+        adapter_type_enum = qapi_enum_parse(&BlockdevVmdkAdapterType_lookup,
+                                            adapter_type,
+                                            BLOCKDEV_VMDK_ADAPTER_TYPE_IDE,
+                                            &local_err);
+        if (local_err) {
+            error_propagate(errp, local_err);
+            ret = -EINVAL;
+            goto exit;
+        }
+    } else {
+        adapter_type_enum = BLOCKDEV_VMDK_ADAPTER_TYPE_IDE;
+    }
+
+    if (!fmt) {
+        /* Default format to monolithicSparse */
+        subformat = BLOCKDEV_VMDK_SUBFORMAT_MONOLITHICSPARSE;
+    } else {
+        subformat = qapi_enum_parse(&BlockdevVmdkSubformat_lookup,
+                                    fmt,
+                                    BLOCKDEV_VMDK_SUBFORMAT_MONOLITHICSPARSE,
+                                    &local_err);
+        if (local_err) {
+            error_propagate(errp, local_err);
+            ret = -EINVAL;
+            goto exit;
+        }
     }
+    data = (VMDKCreateOptsData){
+        .prefix = prefix,
+        .postfix = postfix,
+        .path = path,
+        .opts = opts,
+    };
+    ret = vmdk_co_do_create(total_size, subformat, adapter_type_enum,
+                            backing_file, hw_version, compat6, zeroed_grain,
+                            vmdk_co_create_opts_cb, &data, errp);
+
+exit:
     g_free(adapter_type);
     g_free(backing_file);
     g_free(hw_version);
@@ -2166,7 +2316,86 @@ exit:
     g_free(ext_filename);
     g_free(desc_filename);
     g_free(parent_desc_line);
-    g_string_free(ext_desc_lines, true);
+    return ret;
+}
+
+static BlockBackend *vmdk_co_create_cb(int64_t size, int idx,
+                                       bool flat, bool split, bool compress,
+                                       bool zeroed_grain, void *opaque,
+                                       Error **errp)
+{
+    int ret;
+    BlockDriverState *bs;
+    BlockBackend *blk;
+    BlockdevCreateOptionsVmdk *opts = opaque;
+
+    if (idx == 0) {
+        bs = bdrv_open_blockdev_ref(opts->file, errp);
+    } else {
+        int i;
+        BlockdevRefList *list = opts->extents;
+        for (i = 1; i < idx; i++) {
+            if (!list || !list->next) {
+                error_setg(errp, "Extent [%d] not specified", i);
+                return NULL;
+            }
+            list = list->next;
+        }
+        if (!list) {
+            error_setg(errp, "Extent [%d] not specified", idx - 1);
+            return NULL;
+        }
+        bs = bdrv_open_blockdev_ref(list->value, errp);
+    }
+    if (!bs) {
+        return NULL;
+    }
+    blk = blk_new(BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE | BLK_PERM_RESIZE,
+                  BLK_PERM_ALL);
+    if (blk_insert_bs(blk, bs, errp)) {
+        bdrv_unref(bs);
+        return NULL;
+    }
+    blk_set_allow_write_beyond_eof(blk, true);
+    bdrv_unref(bs);
+
+    if (size != -1) {
+        ret = vmdk_init_extent(blk, size, flat, compress, zeroed_grain, errp);
+        if (ret) {
+            blk_unref(blk);
+            blk = NULL;
+        }
+    }
+    return blk;
+}
+
+static int coroutine_fn vmdk_co_create(BlockdevCreateOptions *create_options,
+                                       Error **errp)
+{
+    int ret;
+    BlockdevCreateOptionsVmdk *opts;
+
+    opts = &create_options->u.vmdk;
+
+    /* Validate options */
+    if (!QEMU_IS_ALIGNED(opts->size, BDRV_SECTOR_SIZE)) {
+        error_setg(errp, "Image size must be a multiple of 512 bytes");
+        ret = -EINVAL;
+        goto out;
+    }
+
+    ret = vmdk_co_do_create(opts->size,
+                            opts->subformat,
+                            opts->adapter_type,
+                            opts->backing_file,
+                            opts->hwversion,
+                            false,
+                            opts->zeroed_grain,
+                            vmdk_co_create_cb,
+                            opts, errp);
+    return ret;
+
+out:
     return ret;
 }
 
@@ -2434,6 +2663,7 @@ static BlockDriver bdrv_vmdk = {
     .bdrv_co_pwrite_zeroes        = vmdk_co_pwrite_zeroes,
     .bdrv_close                   = vmdk_close,
     .bdrv_co_create_opts          = vmdk_co_create_opts,
+    .bdrv_co_create               = vmdk_co_create,
     .bdrv_co_flush_to_disk        = vmdk_co_flush,
     .bdrv_co_block_status         = vmdk_co_block_status,
     .bdrv_get_allocated_file_size = vmdk_get_allocated_file_size,
diff --git a/block/vpc.c b/block/vpc.c
index d886465b7e..52ab717642 100644
--- a/block/vpc.c
+++ b/block/vpc.c
@@ -979,6 +979,7 @@ static int coroutine_fn vpc_co_create(BlockdevCreateOptions *opts,
     int64_t total_size;
     int disk_type;
     int ret = -EIO;
+    QemuUUID uuid;
 
     assert(opts->driver == BLOCKDEV_DRIVER_VPC);
     vpc_opts = &opts->u.vpc;
@@ -1062,7 +1063,8 @@ static int coroutine_fn vpc_co_create(BlockdevCreateOptions *opts,
 
     footer->type = cpu_to_be32(disk_type);
 
-    qemu_uuid_generate(&footer->uuid);
+    qemu_uuid_generate(&uuid);
+    footer->uuid = uuid;
 
     footer->checksum = cpu_to_be32(vpc_checksum(buf, HEADER_SIZE));
 
diff --git a/hw/acpi/vmgenid.c b/hw/acpi/vmgenid.c
index d78b579a20..02717a8b0d 100644
--- a/hw/acpi/vmgenid.c
+++ b/hw/acpi/vmgenid.c
@@ -30,8 +30,7 @@ void vmgenid_build_acpi(VmGenIdState *vms, GArray *table_data, GArray *guid,
      * first, since that's what the guest expects
      */
     g_array_set_size(guid, VMGENID_FW_CFG_SIZE - ARRAY_SIZE(guid_le.data));
-    guid_le = vms->guid;
-    qemu_uuid_bswap(&guid_le);
+    guid_le = qemu_uuid_bswap(vms->guid);
     /* The GUID is written at a fixed offset into the fw_cfg file
      * in order to implement the "OVMF SDT Header probe suppressor"
      * see docs/specs/vmgenid.txt for more details
@@ -149,8 +148,7 @@ static void vmgenid_update_guest(VmGenIdState *vms)
              * however, will expect the fields to be little-endian.
              * Perform a byte swap immediately before writing.
              */
-            guid_le = vms->guid;
-            qemu_uuid_bswap(&guid_le);
+            guid_le = qemu_uuid_bswap(vms->guid);
             /* The GUID is written at a fixed offset into the fw_cfg file
              * in order to implement the "OVMF SDT Header probe suppressor"
              * see docs/specs/vmgenid.txt for more details.
diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c
index 0e9027c8f3..e6db6d7c15 100644
--- a/hw/scsi/scsi-disk.c
+++ b/hw/scsi/scsi-disk.c
@@ -104,6 +104,7 @@ typedef struct SCSIDiskState
     char *serial;
     char *vendor;
     char *product;
+    char *device_id;
     bool tray_open;
     bool tray_locked;
     /*
@@ -642,22 +643,19 @@ static int scsi_disk_emulate_vpd_page(SCSIRequest *req, uint8_t *outbuf)
 
     case 0x83: /* Device identification page, mandatory */
     {
-        const char *str = s->serial ?: blk_name(s->qdev.conf.blk);
-        int max_len = s->serial ? 20 : 255 - 8;
-        int id_len = strlen(str);
+        int id_len = s->device_id ? MIN(strlen(s->device_id), 255 - 8) : 0;
 
-        if (id_len > max_len) {
-            id_len = max_len;
-        }
         DPRINTF("Inquiry EVPD[Device identification] "
                 "buffer size %zd\n", req->cmd.xfer);
 
-        outbuf[buflen++] = 0x2; /* ASCII */
-        outbuf[buflen++] = 0;   /* not officially assigned */
-        outbuf[buflen++] = 0;   /* reserved */
-        outbuf[buflen++] = id_len; /* length of data following */
-        memcpy(outbuf + buflen, str, id_len);
-        buflen += id_len;
+        if (id_len) {
+            outbuf[buflen++] = 0x2; /* ASCII */
+            outbuf[buflen++] = 0;   /* not officially assigned */
+            outbuf[buflen++] = 0;   /* reserved */
+            outbuf[buflen++] = id_len; /* length of data following */
+            memcpy(outbuf + buflen, s->device_id, id_len);
+            buflen += id_len;
+        }
 
         if (s->qdev.wwn) {
             outbuf[buflen++] = 0x1; /* Binary */
@@ -2361,6 +2359,16 @@ static void scsi_realize(SCSIDevice *dev, Error **errp)
     if (!s->vendor) {
         s->vendor = g_strdup("QEMU");
     }
+    if (!s->device_id) {
+        if (s->serial) {
+            s->device_id = g_strdup_printf("%.20s", s->serial);
+        } else {
+            const char *str = blk_name(s->qdev.conf.blk);
+            if (str && *str) {
+                s->device_id = g_strdup(str);
+            }
+        }
+    }
 
     if (blk_is_sg(s->qdev.conf.blk)) {
         error_setg(errp, "unwanted /dev/sg*");
@@ -2381,10 +2389,13 @@ static void scsi_realize(SCSIDevice *dev, Error **errp)
 static void scsi_hd_realize(SCSIDevice *dev, Error **errp)
 {
     SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
+    AioContext *ctx = NULL;
     /* can happen for devices without drive. The error message for missing
      * backend will be issued in scsi_realize
      */
     if (s->qdev.conf.blk) {
+        ctx = blk_get_aio_context(s->qdev.conf.blk);
+        aio_context_acquire(ctx);
         blkconf_blocksizes(&s->qdev.conf);
     }
     s->qdev.blocksize = s->qdev.conf.logical_block_size;
@@ -2393,11 +2404,15 @@ static void scsi_hd_realize(SCSIDevice *dev, Error **errp)
         s->product = g_strdup("QEMU HARDDISK");
     }
     scsi_realize(&s->qdev, errp);
+    if (ctx) {
+        aio_context_release(ctx);
+    }
 }
 
 static void scsi_cd_realize(SCSIDevice *dev, Error **errp)
 {
     SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
+    AioContext *ctx;
     int ret;
 
     if (!dev->conf.blk) {
@@ -2408,6 +2423,8 @@ static void scsi_cd_realize(SCSIDevice *dev, Error **errp)
         assert(ret == 0);
     }
 
+    ctx = blk_get_aio_context(dev->conf.blk);
+    aio_context_acquire(ctx);
     s->qdev.blocksize = 2048;
     s->qdev.type = TYPE_ROM;
     s->features |= 1 << SCSI_DISK_F_REMOVABLE;
@@ -2415,6 +2432,7 @@ static void scsi_cd_realize(SCSIDevice *dev, Error **errp)
         s->product = g_strdup("QEMU CD-ROM");
     }
     scsi_realize(&s->qdev, errp);
+    aio_context_release(ctx);
 }
 
 static void scsi_disk_realize(SCSIDevice *dev, Error **errp)
@@ -2553,6 +2571,7 @@ static int get_device_type(SCSIDiskState *s)
 static void scsi_block_realize(SCSIDevice *dev, Error **errp)
 {
     SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
+    AioContext *ctx;
     int sg_version;
     int rc;
 
@@ -2567,6 +2586,9 @@ static void scsi_block_realize(SCSIDevice *dev, Error **errp)
                           "be removed in a future version");
     }
 
+    ctx = blk_get_aio_context(s->qdev.conf.blk);
+    aio_context_acquire(ctx);
+
     /* check we are using a driver managing SG_IO (version 3 and after) */
     rc = blk_ioctl(s->qdev.conf.blk, SG_GET_VERSION_NUM, &sg_version);
     if (rc < 0) {
@@ -2574,18 +2596,18 @@ static void scsi_block_realize(SCSIDevice *dev, Error **errp)
         if (rc != -EPERM) {
             error_append_hint(errp, "Is this a SCSI device?\n");
         }
-        return;
+        goto out;
     }
     if (sg_version < 30000) {
         error_setg(errp, "scsi generic interface too old");
-        return;
+        goto out;
     }
 
     /* get device type from INQUIRY data */
     rc = get_device_type(s);
     if (rc < 0) {
         error_setg(errp, "INQUIRY failed");
-        return;
+        goto out;
     }
 
     /* Make a guess for the block size, we'll fix it when the guest sends.
@@ -2605,6 +2627,9 @@ static void scsi_block_realize(SCSIDevice *dev, Error **errp)
 
     scsi_realize(&s->qdev, errp);
     scsi_generic_read_device_inquiry(&s->qdev);
+
+out:
+    aio_context_release(ctx);
 }
 
 typedef struct SCSIBlockReq {
@@ -2902,7 +2927,9 @@ static const TypeInfo scsi_disk_base_info = {
     DEFINE_PROP_STRING("ver", SCSIDiskState, version),               \
     DEFINE_PROP_STRING("serial", SCSIDiskState, serial),             \
     DEFINE_PROP_STRING("vendor", SCSIDiskState, vendor),             \
-    DEFINE_PROP_STRING("product", SCSIDiskState, product)
+    DEFINE_PROP_STRING("product", SCSIDiskState, product),           \
+    DEFINE_PROP_STRING("device_id", SCSIDiskState, device_id)
+
 
 static Property scsi_hd_properties[] = {
     DEFINE_SCSI_DISK_PROPERTIES(),
diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c
index 3aa99717e2..eb90288f47 100644
--- a/hw/scsi/virtio-scsi.c
+++ b/hw/scsi/virtio-scsi.c
@@ -791,9 +791,16 @@ static void virtio_scsi_hotplug(HotplugHandler *hotplug_dev, DeviceState *dev,
     SCSIDevice *sd = SCSI_DEVICE(dev);
 
     if (s->ctx && !s->dataplane_fenced) {
+        AioContext *ctx;
         if (blk_op_is_blocked(sd->conf.blk, BLOCK_OP_TYPE_DATAPLANE, errp)) {
             return;
         }
+        ctx = blk_get_aio_context(sd->conf.blk);
+        if (ctx != s->ctx && ctx != qemu_get_aio_context()) {
+            error_setg(errp, "Cannot attach a blockdev that is using "
+                       "a different iothread");
+            return;
+        }
         virtio_scsi_acquire(s);
         blk_set_aio_context(sd->conf.blk, s->ctx);
         virtio_scsi_release(s);
@@ -824,6 +831,12 @@ static void virtio_scsi_hotunplug(HotplugHandler *hotplug_dev, DeviceState *dev,
         virtio_scsi_release(s);
     }
 
+    if (s->ctx) {
+        virtio_scsi_acquire(s);
+        blk_set_aio_context(sd->conf.blk, qemu_get_aio_context());
+        virtio_scsi_release(s);
+    }
+
     qdev_simple_device_unplug_cb(hotplug_dev, dev, errp);
 }
 
diff --git a/include/qemu/units.h b/include/qemu/units.h
index 1c959d182e..692db3fbb2 100644
--- a/include/qemu/units.h
+++ b/include/qemu/units.h
@@ -17,77 +17,4 @@
 #define PiB     (INT64_C(1) << 50)
 #define EiB     (INT64_C(1) << 60)
 
-/*
- * The following lookup table is intended to be used when a literal string of
- * the number of bytes is required (for example if it needs to be stringified).
- * It can also be used for generic shortcuts of power-of-two sizes.
- * This table is generated using the AWK script below:
- *
- *  BEGIN {
- *      suffix="KMGTPE";
- *      for(i=10; i<64; i++) {
- *          val=2**i;
- *          s=substr(suffix, int(i/10), 1);
- *          n=2**(i%10);
- *          pad=21-int(log(n)/log(10));
- *          printf("#define S_%d%siB %*d\n", n, s, pad, val);
- *      }
- *  }
- */
-
-#define S_1KiB                  1024
-#define S_2KiB                  2048
-#define S_4KiB                  4096
-#define S_8KiB                  8192
-#define S_16KiB                16384
-#define S_32KiB                32768
-#define S_64KiB                65536
-#define S_128KiB              131072
-#define S_256KiB              262144
-#define S_512KiB              524288
-#define S_1MiB               1048576
-#define S_2MiB               2097152
-#define S_4MiB               4194304
-#define S_8MiB               8388608
-#define S_16MiB             16777216
-#define S_32MiB             33554432
-#define S_64MiB             67108864
-#define S_128MiB           134217728
-#define S_256MiB           268435456
-#define S_512MiB           536870912
-#define S_1GiB            1073741824
-#define S_2GiB            2147483648
-#define S_4GiB            4294967296
-#define S_8GiB            8589934592
-#define S_16GiB          17179869184
-#define S_32GiB          34359738368
-#define S_64GiB          68719476736
-#define S_128GiB        137438953472
-#define S_256GiB        274877906944
-#define S_512GiB        549755813888
-#define S_1TiB         1099511627776
-#define S_2TiB         2199023255552
-#define S_4TiB         4398046511104
-#define S_8TiB         8796093022208
-#define S_16TiB       17592186044416
-#define S_32TiB       35184372088832
-#define S_64TiB       70368744177664
-#define S_128TiB     140737488355328
-#define S_256TiB     281474976710656
-#define S_512TiB     562949953421312
-#define S_1PiB      1125899906842624
-#define S_2PiB      2251799813685248
-#define S_4PiB      4503599627370496
-#define S_8PiB      9007199254740992
-#define S_16PiB    18014398509481984
-#define S_32PiB    36028797018963968
-#define S_64PiB    72057594037927936
-#define S_128PiB  144115188075855872
-#define S_256PiB  288230376151711744
-#define S_512PiB  576460752303423488
-#define S_1EiB   1152921504606846976
-#define S_2EiB   2305843009213693952
-#define S_4EiB   4611686018427387904
-#define S_8EiB   9223372036854775808
-
 #endif
diff --git a/include/qemu/uuid.h b/include/qemu/uuid.h
index 09489ce5c5..037357d990 100644
--- a/include/qemu/uuid.h
+++ b/include/qemu/uuid.h
@@ -56,6 +56,6 @@ char *qemu_uuid_unparse_strdup(const QemuUUID *uuid);
 
 int qemu_uuid_parse(const char *str, QemuUUID *uuid);
 
-void qemu_uuid_bswap(QemuUUID *uuid);
+QemuUUID qemu_uuid_bswap(QemuUUID uuid);
 
 #endif
diff --git a/include/sysemu/block-backend.h b/include/sysemu/block-backend.h
index c8f816a200..832a4bf168 100644
--- a/include/sysemu/block-backend.h
+++ b/include/sysemu/block-backend.h
@@ -110,9 +110,8 @@ void blk_iostatus_disable(BlockBackend *blk);
 void blk_iostatus_reset(BlockBackend *blk);
 void blk_iostatus_set_err(BlockBackend *blk, int error);
 int blk_attach_dev(BlockBackend *blk, DeviceState *dev);
-void blk_attach_dev_legacy(BlockBackend *blk, void *dev);
-void blk_detach_dev(BlockBackend *blk, void *dev);
-void *blk_get_attached_dev(BlockBackend *blk);
+void blk_detach_dev(BlockBackend *blk, DeviceState *dev);
+DeviceState *blk_get_attached_dev(BlockBackend *blk);
 char *blk_get_attached_dev_id(BlockBackend *blk);
 BlockBackend *blk_by_dev(void *dev);
 BlockBackend *blk_by_qdev_id(const char *id, Error **errp);
diff --git a/qapi/block-core.json b/qapi/block-core.json
index cde1b53708..5f17d67d71 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -4138,6 +4138,76 @@
             '*cluster-size' :   'size' } }
 
 ##
+# @BlockdevVmdkSubformat:
+#
+# Subformat options for VMDK images
+#
+# @monolithicSparse:     Single file image with sparse cluster allocation
+#
+# @monolithicFlat:       Single flat data image and a descriptor file
+#
+# @twoGbMaxExtentSparse: Data is split into 2GB (per virtual LBA) sparse extent
+#                        files, in addition to a descriptor file
+#
+# @twoGbMaxExtentFlat:   Data is split into 2GB (per virtual LBA) flat extent
+#                        files, in addition to a descriptor file
+#
+# @streamOptimized:      Single file image sparse cluster allocation, optimized
+#                        for streaming over network.
+#
+# Since: 4.0
+##
+{ 'enum': 'BlockdevVmdkSubformat',
+  'data': [ 'monolithicSparse', 'monolithicFlat', 'twoGbMaxExtentSparse',
+            'twoGbMaxExtentFlat', 'streamOptimized'] }
+
+##
+# @BlockdevVmdkAdapterType:
+#
+# Adapter type info for VMDK images
+#
+# Since: 4.0
+##
+{ 'enum': 'BlockdevVmdkAdapterType',
+  'data': [ 'ide', 'buslogic', 'lsilogic', 'legacyESX'] }
+
+##
+# @BlockdevCreateOptionsVmdk:
+#
+# Driver specific image creation options for VMDK.
+#
+# @file         Where to store the new image file. This refers to the image
+#               file for monolithcSparse and streamOptimized format, or the
+#               descriptor file for other formats.
+# @size         Size of the virtual disk in bytes
+# @extents      Where to store the data extents. Required for monolithcFlat,
+#               twoGbMaxExtentSparse and twoGbMaxExtentFlat formats. For
+#               monolithicFlat, only one entry is required; for
+#               twoGbMaxExtent* formats, the number of entries required is
+#               calculated as extent_number = virtual_size / 2GB. Providing
+#               more extents than will be used is an error.
+# @subformat    The subformat of the VMDK image. Default: "monolithicSparse".
+# @backing-file The path of backing file. Default: no backing file is used.
+# @adapter-type The adapter type used to fill in the descriptor. Default: ide.
+# @hwversion    Hardware version. The meaningful options are "4" or "6".
+#               Default: "4".
+# @zeroed-grain Whether to enable zeroed-grain feature for sparse subformats.
+#               Default: false.
+#
+# Since: 4.0
+##
+{ 'struct': 'BlockdevCreateOptionsVmdk',
+  'data': { 'file':             'BlockdevRef',
+            'size':             'size',
+            '*extents':          ['BlockdevRef'],
+            '*subformat':       'BlockdevVmdkSubformat',
+            '*backing-file':    'str',
+            '*adapter-type':    'BlockdevVmdkAdapterType',
+            '*hwversion':       'str',
+            '*zeroed-grain':    'bool' } }
+
+
+##
 # @SheepdogRedundancyType:
 #
 # @full             Create a fully replicated vdi with x copies
@@ -4331,6 +4401,7 @@
       'ssh':            'BlockdevCreateOptionsSsh',
       'vdi':            'BlockdevCreateOptionsVdi',
       'vhdx':           'BlockdevCreateOptionsVhdx',
+      'vmdk':           'BlockdevCreateOptionsVmdk',
       'vpc':            'BlockdevCreateOptionsVpc'
   } }
 
diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json
index 3bbdfcee84..1845aa78ff 100644
--- a/qapi/qapi-schema.json
+++ b/qapi/qapi-schema.json
@@ -63,13 +63,15 @@
         'query-tpm-types',
         'ringbuf-read' ],
     'name-case-whitelist': [
-        'ACPISlotType',         # DIMM, visible through query-acpi-ospm-status
-        'CpuInfoMIPS',          # PC, visible through query-cpu
-        'CpuInfoTricore',       # PC, visible through query-cpu
-        'QapiErrorClass',       # all members, visible through errors
-        'UuidInfo',             # UUID, visible through query-uuid
-        'X86CPURegister32',     # all members, visible indirectly through qom-get
-        'q_obj_CpuInfo-base'    # CPU, visible through query-cpu
+        'ACPISlotType',             # DIMM, visible through query-acpi-ospm-status
+        'CpuInfoMIPS',              # PC, visible through query-cpu
+        'CpuInfoTricore',           # PC, visible through query-cpu
+        'BlockdevVmdkSubformat',    # all members, to match VMDK spec spellings
+        'BlockdevVmdkAdapterType',  # legacyESX, to match VMDK spec spellings
+        'QapiErrorClass',           # all members, visible through errors
+        'UuidInfo',                 # UUID, visible through query-uuid
+        'X86CPURegister32',         # all members, visible indirectly through qom-get
+        'q_obj_CpuInfo-base'        # CPU, visible through query-cpu
     ] } }
 
 # Documentation generated with qapi-gen.py is in source order, with
diff --git a/scripts/qtest.py b/scripts/qtest.py
index adf1fe3f26..afac3fe900 100644
--- a/scripts/qtest.py
+++ b/scripts/qtest.py
@@ -31,6 +31,7 @@ class QEMUQtestProtocol(object):
         """
         self._address = address
         self._sock = self._get_sock()
+        self._sockfile = None
         if server:
             self._sock.bind(self._address)
             self._sock.listen(1)
@@ -49,6 +50,7 @@ class QEMUQtestProtocol(object):
         @raise socket.error on socket connection errors
         """
         self._sock.connect(self._address)
+        self._sockfile = self._sock.makefile()
 
     def accept(self):
         """
@@ -57,6 +59,7 @@ class QEMUQtestProtocol(object):
         @raise socket.error on socket connection errors
         """
         self._sock, _ = self._sock.accept()
+        self._sockfile = self._sock.makefile()
 
     def cmd(self, qtest_cmd):
         """
@@ -65,9 +68,12 @@ class QEMUQtestProtocol(object):
         @param qtest_cmd: qtest command text to be sent
         """
         self._sock.sendall((qtest_cmd + "\n").encode('utf-8'))
+        resp = self._sockfile.readline()
+        return resp
 
     def close(self):
         self._sock.close()
+        self._sockfile.close()
 
     def settimeout(self, timeout):
         self._sock.settimeout(timeout)
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 19b4c0a696..75ad9c0dd3 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -73,6 +73,7 @@ check-unit-y += tests/test-bdrv-drain$(EXESUF)
 check-unit-y += tests/test-blockjob$(EXESUF)
 check-unit-y += tests/test-blockjob-txn$(EXESUF)
 check-unit-y += tests/test-block-backend$(EXESUF)
+check-unit-y += tests/test-block-iothread$(EXESUF)
 check-unit-y += tests/test-image-locking$(EXESUF)
 check-unit-y += tests/test-x86-cpuid$(EXESUF)
 # all code tested by test-x86-cpuid is inside topology.h
@@ -557,6 +558,7 @@ tests/test-bdrv-drain$(EXESUF): tests/test-bdrv-drain.o $(test-block-obj-y) $(te
 tests/test-blockjob$(EXESUF): tests/test-blockjob.o $(test-block-obj-y) $(test-util-obj-y)
 tests/test-blockjob-txn$(EXESUF): tests/test-blockjob-txn.o $(test-block-obj-y) $(test-util-obj-y)
 tests/test-block-backend$(EXESUF): tests/test-block-backend.o $(test-block-obj-y) $(test-util-obj-y)
+tests/test-block-iothread$(EXESUF): tests/test-block-iothread.o $(test-block-obj-y) $(test-util-obj-y)
 tests/test-image-locking$(EXESUF): tests/test-image-locking.o $(test-block-obj-y) $(test-util-obj-y)
 tests/test-thread-pool$(EXESUF): tests/test-thread-pool.o $(test-block-obj-y)
 tests/test-iov$(EXESUF): tests/test-iov.o $(test-util-obj-y)
diff --git a/tests/qemu-iotests/141.out b/tests/qemu-iotests/141.out
index f252c86875..41c7291258 100644
--- a/tests/qemu-iotests/141.out
+++ b/tests/qemu-iotests/141.out
@@ -28,7 +28,7 @@ Formatting 'TEST_DIR/o.IMGFMT', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/t.
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "job0"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "job0", "len": 0, "offset": 0, "speed": 0, "type": "mirror"}}
 {"return": {}}
-{"error": {"class": "GenericError", "desc": "Node 'drv0' is busy: node is used as backing hd of 'NODE_NAME'"}}
+{"error": {"class": "GenericError", "desc": "Node 'drv0' is busy: block device is in use by block job: mirror"}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job0"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job0"}}
@@ -45,7 +45,7 @@ Formatting 'TEST_DIR/o.IMGFMT', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/t.
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "job0"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "job0", "len": 0, "offset": 0, "speed": 0, "type": "commit"}}
 {"return": {}}
-{"error": {"class": "GenericError", "desc": "Node 'drv0' is busy: node is used as backing hd of 'NODE_NAME'"}}
+{"error": {"class": "GenericError", "desc": "Node 'drv0' is busy: block device is in use by block job: commit"}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job0"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job0"}}
diff --git a/tests/qemu-iotests/229 b/tests/qemu-iotests/229
index 893d098ad2..b0d4885fa6 100755
--- a/tests/qemu-iotests/229
+++ b/tests/qemu-iotests/229
@@ -81,11 +81,15 @@ echo
 echo '=== Force cancel job paused in error state  ==='
 echo
 
+# Filter out BLOCK_JOB_ERROR events because they may or may not occur.
+# Cancelling the job means resuming it for a bit before it is actually
+# aborted, and in that time it may or may not re-encounter the error.
 success_or_failure="y" _send_qemu_cmd $QEMU_HANDLE \
     "{'execute': 'block-job-cancel',
                  'arguments': { 'device': 'testdisk',
                                 'force': true}}" \
-     "BLOCK_JOB_CANCELLED" "Assertion"
+     "BLOCK_JOB_CANCELLED" "Assertion" \
+    | grep -v '"BLOCK_JOB_ERROR"'
 
 # success, all done
 echo "*** done"
diff --git a/tests/qemu-iotests/229.out b/tests/qemu-iotests/229.out
index 4c4112805f..a3eb33788a 100644
--- a/tests/qemu-iotests/229.out
+++ b/tests/qemu-iotests/229.out
@@ -17,7 +17,6 @@ wrote 2097152/2097152 bytes at offset 0
 
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "testdisk"}}
 {"return": {}}
-{"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_ERROR", "data": {"device": "testdisk", "operation": "write", "action": "stop"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "testdisk"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "BLOCK_JOB_CANCELLED", "data": {"device": "testdisk", "len": 2097152, "offset": 1048576, "speed": 0, "type": "mirror"}}
 *** done
diff --git a/tests/qemu-iotests/234 b/tests/qemu-iotests/234
index a8185b4360..c4c26bc21e 100755
--- a/tests/qemu-iotests/234
+++ b/tests/qemu-iotests/234
@@ -26,6 +26,22 @@ import os
 iotests.verify_image_format(supported_fmts=['qcow2'])
 iotests.verify_platform(['linux'])
 
+def enable_migration_events(vm, name):
+    iotests.log('Enabling migration QMP events on %s...' % name)
+    iotests.log(vm.qmp('migrate-set-capabilities', capabilities=[
+        {
+            'capability': 'events',
+            'state': True
+        }
+    ]))
+
+def wait_migration(vm):
+    while True:
+        event = vm.event_wait('MIGRATION')
+        iotests.log(event, filters=[iotests.filter_qmp_event])
+        if event['data']['status'] == 'completed':
+            break
+
 with iotests.FilePath('img') as img_path, \
      iotests.FilePath('backing') as backing_path, \
      iotests.FilePath('mig_fifo_a') as fifo_a, \
@@ -46,6 +62,8 @@ with iotests.FilePath('img') as img_path, \
          .add_blockdev('%s,file=drive0-backing-file,node-name=drive0-backing' % (iotests.imgfmt))
          .launch())
 
+    enable_migration_events(vm_a, 'A')
+
     iotests.log('Launching destination VM...')
     (vm_b.add_blockdev('file,filename=%s,node-name=drive0-file' % (img_path))
          .add_blockdev('%s,file=drive0-file,node-name=drive0' % (iotests.imgfmt))
@@ -54,6 +72,8 @@ with iotests.FilePath('img') as img_path, \
          .add_incoming("exec: cat '%s'" % (fifo_a))
          .launch())
 
+    enable_migration_events(vm_b, 'B')
+
     # Add a child node that was created after the parent node. The reverse case
     # is covered by the -blockdev options above.
     iotests.log(vm_a.qmp('blockdev-snapshot', node='drive0-backing',
@@ -61,22 +81,13 @@ with iotests.FilePath('img') as img_path, \
     iotests.log(vm_b.qmp('blockdev-snapshot', node='drive0-backing',
                          overlay='drive0'))
 
-    iotests.log('Enabling migration QMP events on A...')
-    iotests.log(vm_a.qmp('migrate-set-capabilities', capabilities=[
-        {
-            'capability': 'events',
-            'state': True
-        }
-    ]))
-
     iotests.log('Starting migration to B...')
     iotests.log(vm_a.qmp('migrate', uri='exec:cat >%s' % (fifo_a)))
     with iotests.Timeout(3, 'Migration does not complete'):
-        while True:
-            event = vm_a.event_wait('MIGRATION')
-            iotests.log(event, filters=[iotests.filter_qmp_event])
-            if event['data']['status'] == 'completed':
-                break
+        # Wait for the source first (which includes setup=setup)
+        wait_migration(vm_a)
+        # Wait for the destination second (which does not)
+        wait_migration(vm_b)
 
     iotests.log(vm_a.qmp('query-migrate')['return']['status'])
     iotests.log(vm_b.qmp('query-migrate')['return']['status'])
@@ -94,25 +105,18 @@ with iotests.FilePath('img') as img_path, \
          .add_incoming("exec: cat '%s'" % (fifo_b))
          .launch())
 
+    enable_migration_events(vm_a, 'A')
+
     iotests.log(vm_a.qmp('blockdev-snapshot', node='drive0-backing',
                          overlay='drive0'))
 
-    iotests.log('Enabling migration QMP events on B...')
-    iotests.log(vm_b.qmp('migrate-set-capabilities', capabilities=[
-        {
-            'capability': 'events',
-            'state': True
-        }
-    ]))
-
     iotests.log('Starting migration back to A...')
     iotests.log(vm_b.qmp('migrate', uri='exec:cat >%s' % (fifo_b)))
     with iotests.Timeout(3, 'Migration does not complete'):
-        while True:
-            event = vm_b.event_wait('MIGRATION')
-            iotests.log(event, filters=[iotests.filter_qmp_event])
-            if event['data']['status'] == 'completed':
-                break
+        # Wait for the source first (which includes setup=setup)
+        wait_migration(vm_b)
+        # Wait for the destination second (which does not)
+        wait_migration(vm_a)
 
     iotests.log(vm_a.qmp('query-migrate')['return']['status'])
     iotests.log(vm_b.qmp('query-migrate')['return']['status'])
diff --git a/tests/qemu-iotests/234.out b/tests/qemu-iotests/234.out
index b9ed910b1a..692976d1c6 100644
--- a/tests/qemu-iotests/234.out
+++ b/tests/qemu-iotests/234.out
@@ -1,14 +1,18 @@
 Launching source VM...
+Enabling migration QMP events on A...
+{"return": {}}
 Launching destination VM...
+Enabling migration QMP events on B...
 {"return": {}}
 {"return": {}}
-Enabling migration QMP events on A...
 {"return": {}}
 Starting migration to B...
 {"return": {}}
 {"data": {"status": "setup"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 {"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 {"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+{"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+{"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 completed
 completed
 {"return": {"running": false, "singlestep": false, "status": "postmigrate"}}
@@ -16,14 +20,16 @@ completed
 Add a second parent to drive0-file...
 {"return": {}}
 Restart A with -incoming and second parent...
+Enabling migration QMP events on A...
 {"return": {}}
-Enabling migration QMP events on B...
 {"return": {}}
 Starting migration back to A...
 {"return": {}}
 {"data": {"status": "setup"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 {"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 {"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+{"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+{"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 completed
 completed
 {"return": {"running": true, "singlestep": false, "status": "running"}}
diff --git a/tests/qemu-iotests/236.out b/tests/qemu-iotests/236.out
index 1dad24db0d..bb2d71ea5e 100644
--- a/tests/qemu-iotests/236.out
+++ b/tests/qemu-iotests/236.out
@@ -45,23 +45,23 @@ write -P0xcd 0x3ff0000 64k
     "actions": [
       {
         "data": {
-          "node": "drive0",
-          "name": "bitmapB"
+          "name": "bitmapB",
+          "node": "drive0"
         },
         "type": "block-dirty-bitmap-disable"
       },
       {
         "data": {
-          "node": "drive0",
+          "granularity": 65536,
           "name": "bitmapC",
-          "granularity": 65536
+          "node": "drive0"
         },
         "type": "block-dirty-bitmap-add"
       },
       {
         "data": {
-          "node": "drive0",
-          "name": "bitmapA"
+          "name": "bitmapA",
+          "node": "drive0"
         },
         "type": "block-dirty-bitmap-clear"
       },
@@ -105,30 +105,30 @@ write -P0xcd 0x3ff0000 64k
     "actions": [
       {
         "data": {
-          "node": "drive0",
-          "name": "bitmapB"
+          "name": "bitmapB",
+          "node": "drive0"
         },
         "type": "block-dirty-bitmap-disable"
       },
       {
         "data": {
-          "node": "drive0",
+          "granularity": 65536,
           "name": "bitmapC",
-          "granularity": 65536
+          "node": "drive0"
         },
         "type": "block-dirty-bitmap-add"
       },
       {
         "data": {
-          "node": "drive0",
-          "name": "bitmapC"
+          "name": "bitmapC",
+          "node": "drive0"
         },
         "type": "block-dirty-bitmap-disable"
       },
       {
         "data": {
-          "node": "drive0",
-          "name": "bitmapC"
+          "name": "bitmapC",
+          "node": "drive0"
         },
         "type": "block-dirty-bitmap-enable"
       }
@@ -158,15 +158,15 @@ write -P0xea 0x3fe0000 64k
     "actions": [
       {
         "data": {
-          "node": "drive0",
-          "name": "bitmapA"
+          "name": "bitmapA",
+          "node": "drive0"
         },
         "type": "block-dirty-bitmap-disable"
       },
       {
         "data": {
-          "node": "drive0",
-          "name": "bitmapC"
+          "name": "bitmapC",
+          "node": "drive0"
         },
         "type": "block-dirty-bitmap-disable"
       }
@@ -209,21 +209,21 @@ write -P0xea 0x3fe0000 64k
     "actions": [
       {
         "data": {
-          "node": "drive0",
           "disabled": true,
+          "granularity": 65536,
           "name": "bitmapD",
-          "granularity": 65536
+          "node": "drive0"
         },
         "type": "block-dirty-bitmap-add"
       },
       {
         "data": {
-          "node": "drive0",
-          "target": "bitmapD",
           "bitmaps": [
             "bitmapB",
             "bitmapC"
-          ]
+          ],
+          "node": "drive0",
+          "target": "bitmapD"
         },
         "type": "block-dirty-bitmap-merge"
       },
@@ -273,21 +273,21 @@ write -P0xea 0x3fe0000 64k
     "actions": [
       {
         "data": {
-          "node": "drive0",
           "disabled": true,
+          "granularity": 65536,
           "name": "bitmapD",
-          "granularity": 65536
+          "node": "drive0"
         },
         "type": "block-dirty-bitmap-add"
       },
       {
         "data": {
-          "node": "drive0",
-          "target": "bitmapD",
           "bitmaps": [
             "bitmapB",
             "bitmapC"
-          ]
+          ],
+          "node": "drive0",
+          "target": "bitmapD"
         },
         "type": "block-dirty-bitmap-merge"
       }
diff --git a/tests/qemu-iotests/237 b/tests/qemu-iotests/237
new file mode 100755
index 0000000000..251771d7fb
--- /dev/null
+++ b/tests/qemu-iotests/237
@@ -0,0 +1,237 @@
+#!/usr/bin/env python
+#
+# Test vmdk and file image creation
+#
+# Copyright (C) 2018 Red Hat, Inc.
+#
+# Creator/Owner: Kevin Wolf <kwolf@redhat.com>
+#
+# 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/>.
+#
+
+import math
+import iotests
+from iotests import imgfmt
+
+iotests.verify_image_format(supported_fmts=['vmdk'])
+
+def blockdev_create(vm, options):
+    result = vm.qmp_log('blockdev-create', job_id='job0', options=options)
+
+    if 'return' in result:
+        assert result['return'] == {}
+        vm.run_job('job0')
+    iotests.log("")
+
+with iotests.FilePath('t.vmdk') as disk_path, \
+     iotests.FilePath('t.vmdk.1') as extent1_path, \
+     iotests.FilePath('t.vmdk.2') as extent2_path, \
+     iotests.FilePath('t.vmdk.3') as extent3_path, \
+     iotests.VM() as vm:
+
+    #
+    # Successful image creation (defaults)
+    #
+    iotests.log("=== Successful image creation (defaults) ===")
+    iotests.log("")
+
+    size = 5 * 1024 * 1024 * 1024
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': 'file',
+                          'filename': disk_path,
+                          'size': 0 })
+
+    vm.qmp_log('blockdev-add', driver='file', filename=disk_path,
+               node_name='imgfile')
+
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': 'imgfile',
+                          'size': size })
+    vm.shutdown()
+
+    iotests.img_info_log(disk_path)
+
+    #
+    # Successful image creation (inline blockdev-add, explicit defaults)
+    #
+    iotests.log("=== Successful image creation (inline blockdev-add, explicit defaults) ===")
+    iotests.log("")
+
+    # Choose a different size to show that we got a new image
+    size = 64 * 1024 * 1024
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': 'file',
+                          'filename': disk_path,
+                          'size': 0 })
+
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': {
+                              'driver': 'file',
+                              'filename': disk_path,
+                          },
+                          'size': size,
+                          'extents': [],
+                          'subformat': 'monolithicSparse',
+                          'adapter-type': 'ide',
+                          'hwversion': '4',
+                          'zeroed-grain': False })
+    vm.shutdown()
+
+    iotests.img_info_log(disk_path)
+
+    #
+    # Successful image creation (non-default options)
+    #
+    iotests.log("=== Successful image creation (with non-default options) ===")
+    iotests.log("")
+
+    # Choose a different size to show that we got a new image
+    size = 32 * 1024 * 1024
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': 'file',
+                          'filename': disk_path,
+                          'size': 0 })
+
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': {
+                              'driver': 'file',
+                              'filename': disk_path,
+                          },
+                          'size': size,
+                          'extents': [],
+                          'subformat': 'monolithicSparse',
+                          'adapter-type': 'buslogic',
+                          'zeroed-grain': True })
+    vm.shutdown()
+
+    iotests.img_info_log(disk_path)
+
+    #
+    # Invalid BlockdevRef
+    #
+    iotests.log("=== Invalid BlockdevRef ===")
+    iotests.log("")
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': "this doesn't exist",
+                          'size': size })
+    vm.shutdown()
+
+    #
+    # Adapter types
+    #
+
+    iotests.log("=== Adapter types ===")
+    iotests.log("")
+
+    vm.add_blockdev('driver=file,filename=%s,node-name=node0' % (disk_path))
+
+    # Valid
+    iotests.log("== Valid adapter types ==")
+    iotests.log("")
+
+    vm.launch()
+    for adapter_type in [ 'ide', 'buslogic', 'lsilogic', 'legacyESX' ]:
+        blockdev_create(vm, { 'driver': imgfmt,
+                              'file': 'node0',
+                              'size': size,
+                              'adapter-type': adapter_type })
+    vm.shutdown()
+
+    # Invalid
+    iotests.log("== Invalid adapter types ==")
+    iotests.log("")
+
+    vm.launch()
+    for adapter_type in [ 'foo', 'IDE', 'legacyesx', 1 ]:
+        blockdev_create(vm, { 'driver': imgfmt,
+                              'file': 'node0',
+                              'size': size,
+                              'adapter-type': adapter_type })
+    vm.shutdown()
+
+    #
+    # Other subformats
+    #
+    iotests.log("=== Other subformats ===")
+    iotests.log("")
+
+    for path in [ extent1_path, extent2_path, extent3_path ]:
+        msg = iotests.qemu_img_pipe('create', '-f', imgfmt, path, '0')
+        iotests.log(msg, [iotests.filter_testfiles])
+
+    vm.add_blockdev('driver=file,filename=%s,node-name=ext1' % (extent1_path))
+    vm.add_blockdev('driver=file,filename=%s,node-name=ext2' % (extent2_path))
+    vm.add_blockdev('driver=file,filename=%s,node-name=ext3' % (extent3_path))
+
+    # Missing extent
+    iotests.log("== Missing extent ==")
+    iotests.log("")
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': 'node0',
+                          'size': size,
+                          'subformat': 'monolithicFlat' })
+    vm.shutdown()
+
+    # Correct extent
+    iotests.log("== Correct extent ==")
+    iotests.log("")
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': 'node0',
+                          'size': size,
+                          'subformat': 'monolithicFlat',
+                          'extents': ['ext1'] })
+    vm.shutdown()
+
+    # Extra extent
+    iotests.log("== Extra extent ==")
+    iotests.log("")
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': 'node0',
+                          'size': 512,
+                          'subformat': 'monolithicFlat',
+                          'extents': ['ext1', 'ext2', 'ext3'] })
+    vm.shutdown()
+
+    # Split formats
+    iotests.log("== Split formats ==")
+    iotests.log("")
+
+    for size in [ 512, 1073741824, 2147483648, 5368709120 ]:
+        for subfmt in [ 'twoGbMaxExtentFlat', 'twoGbMaxExtentSparse' ]:
+            iotests.log("= %s %d =" % (subfmt, size))
+            iotests.log("")
+
+            num_extents = math.ceil(size / 2.0**31)
+            extents = [ "ext%d" % (i) for i in range(1, num_extents + 1) ]
+
+            vm.launch()
+            blockdev_create(vm, { 'driver': imgfmt,
+                                  'file': 'node0',
+                                  'size': size,
+                                  'subformat': subfmt,
+                                  'extents': extents })
+            vm.shutdown()
+
+            iotests.img_info_log(disk_path)
diff --git a/tests/qemu-iotests/237.out b/tests/qemu-iotests/237.out
new file mode 100644
index 0000000000..241c864369
--- /dev/null
+++ b/tests/qemu-iotests/237.out
@@ -0,0 +1,348 @@
+=== Successful image creation (defaults) ===
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "file", "filename": "TEST_DIR/PID-t.vmdk", "size": 0}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+{"execute": "blockdev-add", "arguments": {"driver": "file", "filename": "TEST_DIR/PID-t.vmdk", "node_name": "imgfile"}}
+{"return": {}}
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "file": "imgfile", "size": 5368709120}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+image: TEST_IMG
+file format: IMGFMT
+virtual size: 5.0G (5368709120 bytes)
+cluster_size: 65536
+Format specific information:
+    cid: XXXXXXXXXX
+    parent cid: XXXXXXXXXX
+    create type: monolithicSparse
+    extents:
+        [0]:
+            virtual size: 5368709120
+            filename: TEST_IMG
+            cluster size: 65536
+            format: 
+
+=== Successful image creation (inline blockdev-add, explicit defaults) ===
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "file", "filename": "TEST_DIR/PID-t.vmdk", "size": 0}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"adapter-type": "ide", "driver": "vmdk", "extents": [], "file": {"driver": "file", "filename": "TEST_DIR/PID-t.vmdk"}, "hwversion": "4", "size": 67108864, "subformat": "monolithicSparse", "zeroed-grain": false}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+image: TEST_IMG
+file format: IMGFMT
+virtual size: 64M (67108864 bytes)
+cluster_size: 65536
+Format specific information:
+    cid: XXXXXXXXXX
+    parent cid: XXXXXXXXXX
+    create type: monolithicSparse
+    extents:
+        [0]:
+            virtual size: 67108864
+            filename: TEST_IMG
+            cluster size: 65536
+            format: 
+
+=== Successful image creation (with non-default options) ===
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "file", "filename": "TEST_DIR/PID-t.vmdk", "size": 0}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"adapter-type": "buslogic", "driver": "vmdk", "extents": [], "file": {"driver": "file", "filename": "TEST_DIR/PID-t.vmdk"}, "size": 33554432, "subformat": "monolithicSparse", "zeroed-grain": true}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+image: TEST_IMG
+file format: IMGFMT
+virtual size: 32M (33554432 bytes)
+cluster_size: 65536
+Format specific information:
+    cid: XXXXXXXXXX
+    parent cid: XXXXXXXXXX
+    create type: monolithicSparse
+    extents:
+        [0]:
+            virtual size: 33554432
+            filename: TEST_IMG
+            cluster size: 65536
+            format: 
+
+=== Invalid BlockdevRef ===
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "file": "this doesn't exist", "size": 33554432}}}
+{"return": {}}
+Job failed: Cannot find device=this doesn't exist nor node_name=this doesn't exist
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+=== Adapter types ===
+
+== Valid adapter types ==
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"adapter-type": "ide", "driver": "vmdk", "file": "node0", "size": 33554432}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"adapter-type": "buslogic", "driver": "vmdk", "file": "node0", "size": 33554432}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"adapter-type": "lsilogic", "driver": "vmdk", "file": "node0", "size": 33554432}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"adapter-type": "legacyESX", "driver": "vmdk", "file": "node0", "size": 33554432}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+== Invalid adapter types ==
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"adapter-type": "foo", "driver": "vmdk", "file": "node0", "size": 33554432}}}
+{"error": {"class": "GenericError", "desc": "Invalid parameter 'foo'"}}
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"adapter-type": "IDE", "driver": "vmdk", "file": "node0", "size": 33554432}}}
+{"error": {"class": "GenericError", "desc": "Invalid parameter 'IDE'"}}
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"adapter-type": "legacyesx", "driver": "vmdk", "file": "node0", "size": 33554432}}}
+{"error": {"class": "GenericError", "desc": "Invalid parameter 'legacyesx'"}}
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"adapter-type": 1, "driver": "vmdk", "file": "node0", "size": 33554432}}}
+{"error": {"class": "GenericError", "desc": "Invalid parameter type for 'options.adapter-type', expected: string"}}
+
+=== Other subformats ===
+
+Formatting 'TEST_DIR/PID-t.vmdk.1', fmt=vmdk size=0 compat6=off hwversion=undefined
+
+Formatting 'TEST_DIR/PID-t.vmdk.2', fmt=vmdk size=0 compat6=off hwversion=undefined
+
+Formatting 'TEST_DIR/PID-t.vmdk.3', fmt=vmdk size=0 compat6=off hwversion=undefined
+
+== Missing extent ==
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "file": "node0", "size": 33554432, "subformat": "monolithicFlat"}}}
+{"return": {}}
+Job failed: Extent [0] not specified
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+== Correct extent ==
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 33554432, "subformat": "monolithicFlat"}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+== Extra extent ==
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 512, "subformat": "monolithicFlat"}}}
+{"return": {}}
+Job failed: List of extents contains unused extents
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+== Split formats ==
+
+= twoGbMaxExtentFlat 512 =
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 512, "subformat": "twoGbMaxExtentFlat"}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+image: TEST_IMG
+file format: IMGFMT
+virtual size: 512 (512 bytes)
+Format specific information:
+    cid: XXXXXXXXXX
+    parent cid: XXXXXXXXXX
+    create type: twoGbMaxExtentFlat
+    extents:
+        [0]:
+            virtual size: 512
+            filename: TEST_IMG.1
+            format: FLAT
+
+= twoGbMaxExtentSparse 512 =
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 512, "subformat": "twoGbMaxExtentSparse"}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+image: TEST_IMG
+file format: IMGFMT
+virtual size: 512 (512 bytes)
+cluster_size: 65536
+Format specific information:
+    cid: XXXXXXXXXX
+    parent cid: XXXXXXXXXX
+    create type: twoGbMaxExtentSparse
+    extents:
+        [0]:
+            virtual size: 512
+            filename: TEST_IMG.1
+            cluster size: 65536
+            format: SPARSE
+
+= twoGbMaxExtentFlat 1073741824 =
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 1073741824, "subformat": "twoGbMaxExtentFlat"}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+image: TEST_IMG
+file format: IMGFMT
+virtual size: 1.0G (1073741824 bytes)
+Format specific information:
+    cid: XXXXXXXXXX
+    parent cid: XXXXXXXXXX
+    create type: twoGbMaxExtentFlat
+    extents:
+        [0]:
+            virtual size: 1073741824
+            filename: TEST_IMG.1
+            format: FLAT
+
+= twoGbMaxExtentSparse 1073741824 =
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 1073741824, "subformat": "twoGbMaxExtentSparse"}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+image: TEST_IMG
+file format: IMGFMT
+virtual size: 1.0G (1073741824 bytes)
+cluster_size: 65536
+Format specific information:
+    cid: XXXXXXXXXX
+    parent cid: XXXXXXXXXX
+    create type: twoGbMaxExtentSparse
+    extents:
+        [0]:
+            virtual size: 1073741824
+            filename: TEST_IMG.1
+            cluster size: 65536
+            format: SPARSE
+
+= twoGbMaxExtentFlat 2147483648 =
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 2147483648, "subformat": "twoGbMaxExtentFlat"}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+image: TEST_IMG
+file format: IMGFMT
+virtual size: 2.0G (2147483648 bytes)
+Format specific information:
+    cid: XXXXXXXXXX
+    parent cid: XXXXXXXXXX
+    create type: twoGbMaxExtentFlat
+    extents:
+        [0]:
+            virtual size: 2147483648
+            filename: TEST_IMG.1
+            format: FLAT
+
+= twoGbMaxExtentSparse 2147483648 =
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 2147483648, "subformat": "twoGbMaxExtentSparse"}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+image: TEST_IMG
+file format: IMGFMT
+virtual size: 2.0G (2147483648 bytes)
+cluster_size: 65536
+Format specific information:
+    cid: XXXXXXXXXX
+    parent cid: XXXXXXXXXX
+    create type: twoGbMaxExtentSparse
+    extents:
+        [0]:
+            virtual size: 2147483648
+            filename: TEST_IMG.1
+            cluster size: 65536
+            format: SPARSE
+
+= twoGbMaxExtentFlat 5368709120 =
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 5368709120, "subformat": "twoGbMaxExtentFlat"}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+image: TEST_IMG
+file format: IMGFMT
+virtual size: 5.0G (5368709120 bytes)
+Format specific information:
+    cid: XXXXXXXXXX
+    parent cid: XXXXXXXXXX
+    create type: twoGbMaxExtentFlat
+    extents:
+        [0]:
+            virtual size: 2147483648
+            filename: TEST_IMG.1
+            format: FLAT
+        [1]:
+            virtual size: 2147483648
+            filename: TEST_IMG.2
+            format: FLAT
+        [2]:
+            virtual size: 1073741824
+            filename: TEST_IMG.3
+            format: FLAT
+
+= twoGbMaxExtentSparse 5368709120 =
+
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 5368709120, "subformat": "twoGbMaxExtentSparse"}}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
+image: TEST_IMG
+file format: IMGFMT
+virtual size: 5.0G (5368709120 bytes)
+cluster_size: 65536
+Format specific information:
+    cid: XXXXXXXXXX
+    parent cid: XXXXXXXXXX
+    create type: twoGbMaxExtentSparse
+    extents:
+        [0]:
+            virtual size: 2147483648
+            filename: TEST_IMG.1
+            cluster size: 65536
+            format: SPARSE
+        [1]:
+            virtual size: 2147483648
+            filename: TEST_IMG.2
+            cluster size: 65536
+            format: SPARSE
+        [2]:
+            virtual size: 1073741824
+            filename: TEST_IMG.3
+            cluster size: 65536
+            format: SPARSE
+
diff --git a/tests/qemu-iotests/239 b/tests/qemu-iotests/239
new file mode 100755
index 0000000000..6f085d573d
--- /dev/null
+++ b/tests/qemu-iotests/239
@@ -0,0 +1,53 @@
+#!/bin/bash
+#
+# Test case for dmg
+#
+# Copyright (C) 2019 yuchenlin <npes87184@gmail.com>
+#
+# 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=npes87184@gmail.com
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+status=1	# failure is the default!
+
+_cleanup()
+{
+    rm -f "$TEST_IMG.raw"
+    _cleanup_test_img
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common.rc
+
+_supported_fmt dmg
+_supported_proto file
+_supported_os Linux
+
+echo
+echo "== Testing conversion to raw should success =="
+_use_sample_img simple-dmg.dmg.bz2
+if ! $QEMU_IMG convert -f $IMGFMT -O raw "$TEST_IMG" "$TEST_IMG.raw" ; then
+    exit 1
+fi
+
+# success, all done
+echo "*** done"
+rm -f $seq.full
+status=0
diff --git a/tests/qemu-iotests/239.out b/tests/qemu-iotests/239.out
new file mode 100644
index 0000000000..bedbad065b
--- /dev/null
+++ b/tests/qemu-iotests/239.out
@@ -0,0 +1,4 @@
+QA output created by 239
+
+== Testing conversion to raw should success ==
+*** done
diff --git a/tests/qemu-iotests/240 b/tests/qemu-iotests/240
new file mode 100755
index 0000000000..65cc3b39b1
--- /dev/null
+++ b/tests/qemu-iotests/240
@@ -0,0 +1,129 @@
+#!/bin/bash
+#
+# Test hot plugging and unplugging with iothreads
+#
+# Copyright (C) 2019 Igalia, S.L.
+# Author: Alberto Garcia <berto@igalia.com>
+#
+# 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=berto@igalia.com
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+status=1	# failure is the default!
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+
+_supported_fmt generic
+_supported_proto generic
+_supported_os Linux
+
+do_run_qemu()
+{
+    echo Testing: "$@"
+    $QEMU -nographic -qmp stdio -serial none "$@"
+    echo
+}
+
+# Remove QMP events from (pretty-printed) output. Doesn't handle
+# nested dicts correctly, but we don't get any of those in this test.
+_filter_qmp_events()
+{
+    tr '\n' '\t' | sed -e \
+	's/{\s*"timestamp":\s*{[^}]*},\s*"event":[^,}]*\(,\s*"data":\s*{[^}]*}\)\?\s*}\s*//g' \
+	| tr '\t' '\n'
+}
+
+run_qemu()
+{
+    do_run_qemu "$@" 2>&1 | _filter_qmp | _filter_qmp_events
+}
+
+case "$QEMU_DEFAULT_MACHINE" in
+  s390-ccw-virtio)
+      virtio_scsi=virtio-scsi-ccw
+      ;;
+  *)
+      virtio_scsi=virtio-scsi-pci
+      ;;
+esac
+
+echo
+echo === Unplug a SCSI disk and then plug it again ===
+echo
+
+run_qemu <<EOF
+{ "execute": "qmp_capabilities" }
+{ "execute": "blockdev-add", "arguments": {"driver": "null-co", "node-name": "hd0"}}
+{ "execute": "object-add", "arguments": {"qom-type": "iothread", "id": "iothread0"}}
+{ "execute": "device_add", "arguments": {"id": "scsi0", "driver": "${virtio_scsi}", "iothread": "iothread0"}}
+{ "execute": "device_add", "arguments": {"id": "scsi-hd0", "driver": "scsi-hd", "drive": "hd0"}}
+{ "execute": "device_del", "arguments": {"id": "scsi-hd0"}}
+{ "execute": "device_add", "arguments": {"id": "scsi-hd0", "driver": "scsi-hd", "drive": "hd0"}}
+{ "execute": "device_del", "arguments": {"id": "scsi-hd0"}}
+{ "execute": "device_del", "arguments": {"id": "scsi0"}}
+{ "execute": "blockdev-del", "arguments": {"node-name": "hd0"}}
+{ "execute": "quit"}
+EOF
+
+echo
+echo === Attach two SCSI disks using the same block device and the same iothread ===
+echo
+
+run_qemu <<EOF
+{ "execute": "qmp_capabilities" }
+{ "execute": "blockdev-add", "arguments": {"driver": "null-co", "node-name": "hd0", "read-only": true}}
+{ "execute": "object-add", "arguments": {"qom-type": "iothread", "id": "iothread0"}}
+{ "execute": "device_add", "arguments": {"id": "scsi0", "driver": "${virtio_scsi}", "iothread": "iothread0"}}
+{ "execute": "device_add", "arguments": {"id": "scsi-hd0", "driver": "scsi-hd", "drive": "hd0"}}
+{ "execute": "device_add", "arguments": {"id": "scsi-hd1", "driver": "scsi-hd", "drive": "hd0"}}
+{ "execute": "device_del", "arguments": {"id": "scsi-hd0"}}
+{ "execute": "device_del", "arguments": {"id": "scsi-hd1"}}
+{ "execute": "device_del", "arguments": {"id": "scsi0"}}
+{ "execute": "blockdev-del", "arguments": {"node-name": "hd0"}}
+{ "execute": "quit"}
+EOF
+
+echo
+echo === Attach two SCSI disks using the same block device but different iothreads ===
+echo
+
+run_qemu <<EOF
+{ "execute": "qmp_capabilities" }
+{ "execute": "blockdev-add", "arguments": {"driver": "null-co", "node-name": "hd0", "read-only": true}}
+{ "execute": "object-add", "arguments": {"qom-type": "iothread", "id": "iothread0"}}
+{ "execute": "object-add", "arguments": {"qom-type": "iothread", "id": "iothread1"}}
+{ "execute": "device_add", "arguments": {"id": "scsi0", "driver": "${virtio_scsi}", "iothread": "iothread0"}}
+{ "execute": "device_add", "arguments": {"id": "scsi1", "driver": "${virtio_scsi}", "iothread": "iothread1"}}
+{ "execute": "device_add", "arguments": {"id": "scsi-hd0", "driver": "scsi-hd", "drive": "hd0", "bus": "scsi0.0"}}
+{ "execute": "device_add", "arguments": {"id": "scsi-hd1", "driver": "scsi-hd", "drive": "hd0", "bus": "scsi1.0"}}
+{ "execute": "device_del", "arguments": {"id": "scsi-hd0"}}
+{ "execute": "device_add", "arguments": {"id": "scsi-hd1", "driver": "scsi-hd", "drive": "hd0", "bus": "scsi1.0"}}
+{ "execute": "device_del", "arguments": {"id": "scsi-hd1"}}
+{ "execute": "device_del", "arguments": {"id": "scsi0"}}
+{ "execute": "device_del", "arguments": {"id": "scsi1"}}
+{ "execute": "blockdev-del", "arguments": {"node-name": "hd0"}}
+{ "execute": "quit"}
+EOF
+
+# success, all done
+echo "*** done"
+rm -f $seq.full
+status=0
diff --git a/tests/qemu-iotests/240.out b/tests/qemu-iotests/240.out
new file mode 100644
index 0000000000..d76392966c
--- /dev/null
+++ b/tests/qemu-iotests/240.out
@@ -0,0 +1,54 @@
+QA output created by 240
+
+=== Unplug a SCSI disk and then plug it again ===
+
+Testing:
+QMP_VERSION
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+
+=== Attach two SCSI disks using the same block device and the same iothread ===
+
+Testing:
+QMP_VERSION
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+
+=== Attach two SCSI disks using the same block device but different iothreads ===
+
+Testing:
+QMP_VERSION
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"error": {"class": "GenericError", "desc": "Cannot attach a blockdev that is using a different iothread"}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+{"return": {}}
+*** done
diff --git a/tests/qemu-iotests/check b/tests/qemu-iotests/check
index 89ed275988..895e1e3dcb 100755
--- a/tests/qemu-iotests/check
+++ b/tests/qemu-iotests/check
@@ -237,6 +237,7 @@ image format options
     -vhdx               test vhdx
     -vmdk               test vmdk
     -luks               test luks
+    -dmg                test dmg
 
 image protocol options
     -file               test file (default)
@@ -304,6 +305,12 @@ testlist options
             xpand=false
             ;;
 
+        -dmg)
+            IMGFMT=dmg
+            IMGFMT_GENERIC=false
+            xpand=false
+            ;;
+
         -qed)
             IMGFMT=qed
             xpand=false
diff --git a/tests/qemu-iotests/common.filter b/tests/qemu-iotests/common.filter
index 2031e353a5..1aa7d57140 100644
--- a/tests/qemu-iotests/common.filter
+++ b/tests/qemu-iotests/common.filter
@@ -165,6 +165,7 @@ _filter_img_info()
         -e "/table_size: [0-9]\\+/d" \
         -e "/compat: '[^']*'/d" \
         -e "/compat6: \\(on\\|off\\)/d" \
+        -e "s/cid: [0-9]\+/cid: XXXXXXXXXX/" \
         -e "/static: \\(on\\|off\\)/d" \
         -e "/zeroed_grain: \\(on\\|off\\)/d" \
         -e "/subformat: '[^']*'/d" \
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index 0f1c3f9cdf..959ffe85fc 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -234,4 +234,7 @@
 234 auto quick migration
 235 auto quick
 236 auto quick
+237 rw auto quick
 238 auto quick
+239 rw auto quick
+240 auto quick
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index 009c614ef7..b461f53abf 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -76,15 +76,16 @@ def qemu_img(*args):
         sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
     return exitcode
 
-def ordered_kwargs(kwargs):
-    # kwargs prior to 3.6 are not ordered, so:
-    od = OrderedDict()
-    for k, v in sorted(kwargs.items()):
-        if isinstance(v, dict):
-            od[k] = ordered_kwargs(v)
-        else:
-            od[k] = v
-    return od
+def ordered_qmp(qmsg):
+    # Dictionaries are not ordered prior to 3.6, therefore:
+    if isinstance(qmsg, list):
+        return [ordered_qmp(atom) for atom in qmsg]
+    if isinstance(qmsg, dict):
+        od = OrderedDict()
+        for k, v in sorted(qmsg.items()):
+            od[k] = ordered_qmp(v)
+        return od
+    return qmsg
 
 def qemu_img_create(*args):
     args = list(args)
@@ -299,6 +300,7 @@ def filter_img_info(output, filename):
                    .replace(imgfmt, 'IMGFMT')
         line = re.sub('iters: [0-9]+', 'iters: XXX', line)
         line = re.sub('uuid: [-a-f0-9]+', 'uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', line)
+        line = re.sub('cid: [0-9]+', 'cid: XXXXXXXXXX', line)
         lines.append(line)
     return '\n'.join(lines)
 
@@ -505,7 +507,7 @@ class VM(qtest.QEMUQtestMachine):
     def qmp_log(self, cmd, filters=[], indent=None, **kwargs):
         full_cmd = OrderedDict((
             ("execute", cmd),
-            ("arguments", ordered_kwargs(kwargs))
+            ("arguments", ordered_qmp(kwargs))
         ))
         log(full_cmd, filters, indent=indent)
         result = self.qmp(cmd, **kwargs)
diff --git a/tests/qemu-iotests/sample_images/simple-dmg.dmg.bz2 b/tests/qemu-iotests/sample_images/simple-dmg.dmg.bz2
new file mode 100644
index 0000000000..05e719d03d
--- /dev/null
+++ b/tests/qemu-iotests/sample_images/simple-dmg.dmg.bz2
Binary files differdiff --git a/tests/test-block-iothread.c b/tests/test-block-iothread.c
new file mode 100644
index 0000000000..97ac0b159d
--- /dev/null
+++ b/tests/test-block-iothread.c
@@ -0,0 +1,372 @@
+/*
+ * Block tests for iothreads
+ *
+ * Copyright (c) 2018 Kevin Wolf <kwolf@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "block/block.h"
+#include "block/blockjob_int.h"
+#include "sysemu/block-backend.h"
+#include "qapi/error.h"
+#include "iothread.h"
+
+static int coroutine_fn bdrv_test_co_prwv(BlockDriverState *bs,
+                                          uint64_t offset, uint64_t bytes,
+                                          QEMUIOVector *qiov, int flags)
+{
+    return 0;
+}
+
+static int coroutine_fn bdrv_test_co_pdiscard(BlockDriverState *bs,
+                                              int64_t offset, int bytes)
+{
+    return 0;
+}
+
+static int coroutine_fn
+bdrv_test_co_truncate(BlockDriverState *bs, int64_t offset,
+                      PreallocMode prealloc, Error **errp)
+{
+    return 0;
+}
+
+static int coroutine_fn bdrv_test_co_block_status(BlockDriverState *bs,
+                                                  bool want_zero,
+                                                  int64_t offset, int64_t count,
+                                                  int64_t *pnum, int64_t *map,
+                                                  BlockDriverState **file)
+{
+    *pnum = count;
+    return 0;
+}
+
+static BlockDriver bdrv_test = {
+    .format_name            = "test",
+    .instance_size          = 1,
+
+    .bdrv_co_preadv         = bdrv_test_co_prwv,
+    .bdrv_co_pwritev        = bdrv_test_co_prwv,
+    .bdrv_co_pdiscard       = bdrv_test_co_pdiscard,
+    .bdrv_co_truncate       = bdrv_test_co_truncate,
+    .bdrv_co_block_status   = bdrv_test_co_block_status,
+};
+
+static void test_sync_op_pread(BdrvChild *c)
+{
+    uint8_t buf[512];
+    int ret;
+
+    /* Success */
+    ret = bdrv_pread(c, 0, buf, sizeof(buf));
+    g_assert_cmpint(ret, ==, 512);
+
+    /* Early error: Negative offset */
+    ret = bdrv_pread(c, -2, buf, sizeof(buf));
+    g_assert_cmpint(ret, ==, -EIO);
+}
+
+static void test_sync_op_pwrite(BdrvChild *c)
+{
+    uint8_t buf[512];
+    int ret;
+
+    /* Success */
+    ret = bdrv_pwrite(c, 0, buf, sizeof(buf));
+    g_assert_cmpint(ret, ==, 512);
+
+    /* Early error: Negative offset */
+    ret = bdrv_pwrite(c, -2, buf, sizeof(buf));
+    g_assert_cmpint(ret, ==, -EIO);
+}
+
+static void test_sync_op_blk_pread(BlockBackend *blk)
+{
+    uint8_t buf[512];
+    int ret;
+
+    /* Success */
+    ret = blk_pread(blk, 0, buf, sizeof(buf));
+    g_assert_cmpint(ret, ==, 512);
+
+    /* Early error: Negative offset */
+    ret = blk_pread(blk, -2, buf, sizeof(buf));
+    g_assert_cmpint(ret, ==, -EIO);
+}
+
+static void test_sync_op_blk_pwrite(BlockBackend *blk)
+{
+    uint8_t buf[512];
+    int ret;
+
+    /* Success */
+    ret = blk_pwrite(blk, 0, buf, sizeof(buf), 0);
+    g_assert_cmpint(ret, ==, 512);
+
+    /* Early error: Negative offset */
+    ret = blk_pwrite(blk, -2, buf, sizeof(buf), 0);
+    g_assert_cmpint(ret, ==, -EIO);
+}
+
+static void test_sync_op_load_vmstate(BdrvChild *c)
+{
+    uint8_t buf[512];
+    int ret;
+
+    /* Error: Driver does not support snapshots */
+    ret = bdrv_load_vmstate(c->bs, buf, 0, sizeof(buf));
+    g_assert_cmpint(ret, ==, -ENOTSUP);
+}
+
+static void test_sync_op_save_vmstate(BdrvChild *c)
+{
+    uint8_t buf[512];
+    int ret;
+
+    /* Error: Driver does not support snapshots */
+    ret = bdrv_save_vmstate(c->bs, buf, 0, sizeof(buf));
+    g_assert_cmpint(ret, ==, -ENOTSUP);
+}
+
+static void test_sync_op_pdiscard(BdrvChild *c)
+{
+    int ret;
+
+    /* Normal success path */
+    c->bs->open_flags |= BDRV_O_UNMAP;
+    ret = bdrv_pdiscard(c, 0, 512);
+    g_assert_cmpint(ret, ==, 0);
+
+    /* Early success: UNMAP not supported */
+    c->bs->open_flags &= ~BDRV_O_UNMAP;
+    ret = bdrv_pdiscard(c, 0, 512);
+    g_assert_cmpint(ret, ==, 0);
+
+    /* Early error: Negative offset */
+    ret = bdrv_pdiscard(c, -2, 512);
+    g_assert_cmpint(ret, ==, -EIO);
+}
+
+static void test_sync_op_blk_pdiscard(BlockBackend *blk)
+{
+    int ret;
+
+    /* Early success: UNMAP not supported */
+    ret = blk_pdiscard(blk, 0, 512);
+    g_assert_cmpint(ret, ==, 0);
+
+    /* Early error: Negative offset */
+    ret = blk_pdiscard(blk, -2, 512);
+    g_assert_cmpint(ret, ==, -EIO);
+}
+
+static void test_sync_op_truncate(BdrvChild *c)
+{
+    int ret;
+
+    /* Normal success path */
+    ret = bdrv_truncate(c, 65536, PREALLOC_MODE_OFF, NULL);
+    g_assert_cmpint(ret, ==, 0);
+
+    /* Early error: Negative offset */
+    ret = bdrv_truncate(c, -2, PREALLOC_MODE_OFF, NULL);
+    g_assert_cmpint(ret, ==, -EINVAL);
+
+    /* Error: Read-only image */
+    c->bs->read_only = true;
+    c->bs->open_flags &= ~BDRV_O_RDWR;
+
+    ret = bdrv_truncate(c, 65536, PREALLOC_MODE_OFF, NULL);
+    g_assert_cmpint(ret, ==, -EACCES);
+
+    c->bs->read_only = false;
+    c->bs->open_flags |= BDRV_O_RDWR;
+}
+
+static void test_sync_op_block_status(BdrvChild *c)
+{
+    int ret;
+    int64_t n;
+
+    /* Normal success path */
+    ret = bdrv_is_allocated(c->bs, 0, 65536, &n);
+    g_assert_cmpint(ret, ==, 0);
+
+    /* Early success: No driver support */
+    bdrv_test.bdrv_co_block_status = NULL;
+    ret = bdrv_is_allocated(c->bs, 0, 65536, &n);
+    g_assert_cmpint(ret, ==, 1);
+
+    /* Early success: bytes = 0 */
+    ret = bdrv_is_allocated(c->bs, 0, 0, &n);
+    g_assert_cmpint(ret, ==, 0);
+
+    /* Early success: Offset > image size*/
+    ret = bdrv_is_allocated(c->bs, 0x1000000, 0x1000000, &n);
+    g_assert_cmpint(ret, ==, 0);
+}
+
+static void test_sync_op_flush(BdrvChild *c)
+{
+    int ret;
+
+    /* Normal success path */
+    ret = bdrv_flush(c->bs);
+    g_assert_cmpint(ret, ==, 0);
+
+    /* Early success: Read-only image */
+    c->bs->read_only = true;
+    c->bs->open_flags &= ~BDRV_O_RDWR;
+
+    ret = bdrv_flush(c->bs);
+    g_assert_cmpint(ret, ==, 0);
+
+    c->bs->read_only = false;
+    c->bs->open_flags |= BDRV_O_RDWR;
+}
+
+static void test_sync_op_blk_flush(BlockBackend *blk)
+{
+    BlockDriverState *bs = blk_bs(blk);
+    int ret;
+
+    /* Normal success path */
+    ret = blk_flush(blk);
+    g_assert_cmpint(ret, ==, 0);
+
+    /* Early success: Read-only image */
+    bs->read_only = true;
+    bs->open_flags &= ~BDRV_O_RDWR;
+
+    ret = blk_flush(blk);
+    g_assert_cmpint(ret, ==, 0);
+
+    bs->read_only = false;
+    bs->open_flags |= BDRV_O_RDWR;
+}
+
+static void test_sync_op_check(BdrvChild *c)
+{
+    BdrvCheckResult result;
+    int ret;
+
+    /* Error: Driver does not implement check */
+    ret = bdrv_check(c->bs, &result, 0);
+    g_assert_cmpint(ret, ==, -ENOTSUP);
+}
+
+static void test_sync_op_invalidate_cache(BdrvChild *c)
+{
+    /* Early success: Image is not inactive */
+    bdrv_invalidate_cache(c->bs, NULL);
+}
+
+
+typedef struct SyncOpTest {
+    const char *name;
+    void (*fn)(BdrvChild *c);
+    void (*blkfn)(BlockBackend *blk);
+} SyncOpTest;
+
+const SyncOpTest sync_op_tests[] = {
+    {
+        .name   = "/sync-op/pread",
+        .fn     = test_sync_op_pread,
+        .blkfn  = test_sync_op_blk_pread,
+    }, {
+        .name   = "/sync-op/pwrite",
+        .fn     = test_sync_op_pwrite,
+        .blkfn  = test_sync_op_blk_pwrite,
+    }, {
+        .name   = "/sync-op/load_vmstate",
+        .fn     = test_sync_op_load_vmstate,
+    }, {
+        .name   = "/sync-op/save_vmstate",
+        .fn     = test_sync_op_save_vmstate,
+    }, {
+        .name   = "/sync-op/pdiscard",
+        .fn     = test_sync_op_pdiscard,
+        .blkfn  = test_sync_op_blk_pdiscard,
+    }, {
+        .name   = "/sync-op/truncate",
+        .fn     = test_sync_op_truncate,
+    }, {
+        .name   = "/sync-op/block_status",
+        .fn     = test_sync_op_block_status,
+    }, {
+        .name   = "/sync-op/flush",
+        .fn     = test_sync_op_flush,
+        .blkfn  = test_sync_op_blk_flush,
+    }, {
+        .name   = "/sync-op/check",
+        .fn     = test_sync_op_check,
+    }, {
+        .name   = "/sync-op/invalidate_cache",
+        .fn     = test_sync_op_invalidate_cache,
+    },
+};
+
+/* Test synchronous operations that run in a different iothread, so we have to
+ * poll for the coroutine there to return. */
+static void test_sync_op(const void *opaque)
+{
+    const SyncOpTest *t = opaque;
+    IOThread *iothread = iothread_new();
+    AioContext *ctx = iothread_get_aio_context(iothread);
+    BlockBackend *blk;
+    BlockDriverState *bs;
+    BdrvChild *c;
+
+    blk = blk_new(BLK_PERM_ALL, BLK_PERM_ALL);
+    bs = bdrv_new_open_driver(&bdrv_test, "base", BDRV_O_RDWR, &error_abort);
+    bs->total_sectors = 65536 / BDRV_SECTOR_SIZE;
+    blk_insert_bs(blk, bs, &error_abort);
+    c = QLIST_FIRST(&bs->parents);
+
+    blk_set_aio_context(blk, ctx);
+    aio_context_acquire(ctx);
+    t->fn(c);
+    if (t->blkfn) {
+        t->blkfn(blk);
+    }
+    aio_context_release(ctx);
+    blk_set_aio_context(blk, qemu_get_aio_context());
+
+    bdrv_unref(bs);
+    blk_unref(blk);
+}
+
+int main(int argc, char **argv)
+{
+    int i;
+
+    bdrv_init();
+    qemu_init_main_loop(&error_abort);
+
+    g_test_init(&argc, &argv, NULL);
+
+    for (i = 0; i < ARRAY_SIZE(sync_op_tests); i++) {
+        const SyncOpTest *t = &sync_op_tests[i];
+        g_test_add_data_func(t->name, t, test_sync_op);
+    }
+
+    return g_test_run();
+}
diff --git a/tests/vmgenid-test.c b/tests/vmgenid-test.c
index 52cdd83ec0..ae38ee5ac0 100644
--- a/tests/vmgenid-test.c
+++ b/tests/vmgenid-test.c
@@ -88,7 +88,7 @@ static void read_guid_from_memory(QTestState *qts, QemuUUID *guid)
     /* The GUID is in little-endian format in the guest, while QEMU
      * uses big-endian.  Swap after reading.
      */
-    qemu_uuid_bswap(guid);
+    *guid = qemu_uuid_bswap(*guid);
 }
 
 static void read_guid_from_monitor(QTestState *qts, QemuUUID *guid)
diff --git a/util/uuid.c b/util/uuid.c
index ebf06c049a..5787f0978c 100644
--- a/util/uuid.c
+++ b/util/uuid.c
@@ -110,10 +110,10 @@ int qemu_uuid_parse(const char *str, QemuUUID *uuid)
 
 /* Swap from UUID format endian (BE) to the opposite or vice versa.
  */
-void qemu_uuid_bswap(QemuUUID *uuid)
+QemuUUID qemu_uuid_bswap(QemuUUID uuid)
 {
-    assert(QEMU_PTR_IS_ALIGNED(uuid, sizeof(uint32_t)));
-    bswap32s(&uuid->fields.time_low);
-    bswap16s(&uuid->fields.time_mid);
-    bswap16s(&uuid->fields.time_high_and_version);
+    bswap32s(&uuid.fields.time_low);
+    bswap16s(&uuid.fields.time_mid);
+    bswap16s(&uuid.fields.time_high_and_version);
+    return uuid;
 }