qcow2: Implement copy offloading
The two callbacks are implemented quite similarly to the read/write
functions: bdrv_co_copy_range_from maps for read and calls into bs->file
or bs->backing depending on the allocation status; bdrv_co_copy_range_to
maps for write and calls into bs->file.
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Signed-off-by: Fam Zheng <famz@redhat.com>
Message-id: 20180601092648.24614-5-famz@redhat.com
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
diff --git a/block/qcow2.c b/block/qcow2.c
index a007dc4..79e5ed7 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -1761,6 +1761,39 @@
return status;
}
+static coroutine_fn int qcow2_handle_l2meta(BlockDriverState *bs,
+ QCowL2Meta **pl2meta,
+ bool link_l2)
+{
+ int ret = 0;
+ QCowL2Meta *l2meta = *pl2meta;
+
+ while (l2meta != NULL) {
+ QCowL2Meta *next;
+
+ if (!ret && link_l2) {
+ ret = qcow2_alloc_cluster_link_l2(bs, l2meta);
+ if (ret) {
+ goto out;
+ }
+ }
+
+ /* Take the request off the list of running requests */
+ if (l2meta->nb_clusters != 0) {
+ QLIST_REMOVE(l2meta, next_in_flight);
+ }
+
+ qemu_co_queue_restart_all(&l2meta->dependent_requests);
+
+ next = l2meta->next;
+ g_free(l2meta);
+ l2meta = next;
+ }
+out:
+ *pl2meta = l2meta;
+ return ret;
+}
+
static coroutine_fn int qcow2_co_preadv(BlockDriverState *bs, uint64_t offset,
uint64_t bytes, QEMUIOVector *qiov,
int flags)
@@ -2047,24 +2080,9 @@
}
}
- while (l2meta != NULL) {
- QCowL2Meta *next;
-
- ret = qcow2_alloc_cluster_link_l2(bs, l2meta);
- if (ret < 0) {
- goto fail;
- }
-
- /* Take the request off the list of running requests */
- if (l2meta->nb_clusters != 0) {
- QLIST_REMOVE(l2meta, next_in_flight);
- }
-
- qemu_co_queue_restart_all(&l2meta->dependent_requests);
-
- next = l2meta->next;
- g_free(l2meta);
- l2meta = next;
+ ret = qcow2_handle_l2meta(bs, &l2meta, true);
+ if (ret) {
+ goto fail;
}
bytes -= cur_bytes;
@@ -2075,18 +2093,7 @@
ret = 0;
fail:
- while (l2meta != NULL) {
- QCowL2Meta *next;
-
- if (l2meta->nb_clusters != 0) {
- QLIST_REMOVE(l2meta, next_in_flight);
- }
- qemu_co_queue_restart_all(&l2meta->dependent_requests);
-
- next = l2meta->next;
- g_free(l2meta);
- l2meta = next;
- }
+ qcow2_handle_l2meta(bs, &l2meta, false);
qemu_co_mutex_unlock(&s->lock);
@@ -3273,6 +3280,166 @@
return ret;
}
+static int coroutine_fn
+qcow2_co_copy_range_from(BlockDriverState *bs,
+ BdrvChild *src, uint64_t src_offset,
+ BdrvChild *dst, uint64_t dst_offset,
+ uint64_t bytes, BdrvRequestFlags flags)
+{
+ BDRVQcow2State *s = bs->opaque;
+ int ret;
+ unsigned int cur_bytes; /* number of bytes in current iteration */
+ BdrvChild *child = NULL;
+ BdrvRequestFlags cur_flags;
+
+ assert(!bs->encrypted);
+ qemu_co_mutex_lock(&s->lock);
+
+ while (bytes != 0) {
+ uint64_t copy_offset = 0;
+ /* prepare next request */
+ cur_bytes = MIN(bytes, INT_MAX);
+ cur_flags = flags;
+
+ ret = qcow2_get_cluster_offset(bs, src_offset, &cur_bytes, ©_offset);
+ if (ret < 0) {
+ goto out;
+ }
+
+ switch (ret) {
+ case QCOW2_CLUSTER_UNALLOCATED:
+ if (bs->backing && bs->backing->bs) {
+ int64_t backing_length = bdrv_getlength(bs->backing->bs);
+ if (src_offset >= backing_length) {
+ cur_flags |= BDRV_REQ_ZERO_WRITE;
+ } else {
+ child = bs->backing;
+ cur_bytes = MIN(cur_bytes, backing_length - src_offset);
+ copy_offset = src_offset;
+ }
+ } else {
+ cur_flags |= BDRV_REQ_ZERO_WRITE;
+ }
+ break;
+
+ case QCOW2_CLUSTER_ZERO_PLAIN:
+ case QCOW2_CLUSTER_ZERO_ALLOC:
+ cur_flags |= BDRV_REQ_ZERO_WRITE;
+ break;
+
+ case QCOW2_CLUSTER_COMPRESSED:
+ ret = -ENOTSUP;
+ goto out;
+ break;
+
+ case QCOW2_CLUSTER_NORMAL:
+ child = bs->file;
+ copy_offset += offset_into_cluster(s, src_offset);
+ if ((copy_offset & 511) != 0) {
+ ret = -EIO;
+ goto out;
+ }
+ break;
+
+ default:
+ abort();
+ }
+ qemu_co_mutex_unlock(&s->lock);
+ ret = bdrv_co_copy_range_from(child,
+ copy_offset,
+ dst, dst_offset,
+ cur_bytes, cur_flags);
+ qemu_co_mutex_lock(&s->lock);
+ if (ret < 0) {
+ goto out;
+ }
+
+ bytes -= cur_bytes;
+ src_offset += cur_bytes;
+ dst_offset += cur_bytes;
+ }
+ ret = 0;
+
+out:
+ qemu_co_mutex_unlock(&s->lock);
+ return ret;
+}
+
+static int coroutine_fn
+qcow2_co_copy_range_to(BlockDriverState *bs,
+ BdrvChild *src, uint64_t src_offset,
+ BdrvChild *dst, uint64_t dst_offset,
+ uint64_t bytes, BdrvRequestFlags flags)
+{
+ BDRVQcow2State *s = bs->opaque;
+ int offset_in_cluster;
+ int ret;
+ unsigned int cur_bytes; /* number of sectors in current iteration */
+ uint64_t cluster_offset;
+ uint8_t *cluster_data = NULL;
+ QCowL2Meta *l2meta = NULL;
+
+ assert(!bs->encrypted);
+ s->cluster_cache_offset = -1; /* disable compressed cache */
+
+ qemu_co_mutex_lock(&s->lock);
+
+ while (bytes != 0) {
+
+ l2meta = NULL;
+
+ offset_in_cluster = offset_into_cluster(s, dst_offset);
+ cur_bytes = MIN(bytes, INT_MAX);
+
+ /* TODO:
+ * If src->bs == dst->bs, we could simply copy by incrementing
+ * the refcnt, without copying user data.
+ * Or if src->bs == dst->bs->backing->bs, we could copy by discarding. */
+ ret = qcow2_alloc_cluster_offset(bs, dst_offset, &cur_bytes,
+ &cluster_offset, &l2meta);
+ if (ret < 0) {
+ goto fail;
+ }
+
+ assert((cluster_offset & 511) == 0);
+
+ ret = qcow2_pre_write_overlap_check(bs, 0,
+ cluster_offset + offset_in_cluster, cur_bytes);
+ if (ret < 0) {
+ goto fail;
+ }
+
+ qemu_co_mutex_unlock(&s->lock);
+ ret = bdrv_co_copy_range_to(src, src_offset,
+ bs->file,
+ cluster_offset + offset_in_cluster,
+ cur_bytes, flags);
+ qemu_co_mutex_lock(&s->lock);
+ if (ret < 0) {
+ goto fail;
+ }
+
+ ret = qcow2_handle_l2meta(bs, &l2meta, true);
+ if (ret) {
+ goto fail;
+ }
+
+ bytes -= cur_bytes;
+ dst_offset += cur_bytes;
+ }
+ ret = 0;
+
+fail:
+ qcow2_handle_l2meta(bs, &l2meta, false);
+
+ qemu_co_mutex_unlock(&s->lock);
+
+ qemu_vfree(cluster_data);
+ trace_qcow2_writev_done_req(qemu_coroutine_self(), ret);
+
+ return ret;
+}
+
static int qcow2_truncate(BlockDriverState *bs, int64_t offset,
PreallocMode prealloc, Error **errp)
{
@@ -4521,6 +4688,8 @@
.bdrv_co_pwrite_zeroes = qcow2_co_pwrite_zeroes,
.bdrv_co_pdiscard = qcow2_co_pdiscard,
+ .bdrv_co_copy_range_from = qcow2_co_copy_range_from,
+ .bdrv_co_copy_range_to = qcow2_co_copy_range_to,
.bdrv_truncate = qcow2_truncate,
.bdrv_co_pwritev_compressed = qcow2_co_pwritev_compressed,
.bdrv_make_empty = qcow2_make_empty,