When formatting a qcow2 image, we only need the WRITE permission on the
data-file child to preallocate data, so for metadata-only preallocation
(or none at all), we can suppress that permission via the
BDRV_O_NO_DATA_WRITE flag.
Similarly, we will only resize the data-file if it is currently smaller
than the supposed virtual disk size; so it is already big enough, we can
suppress the RESIZE permission via the BDRV_O_NO_DATA_RESIZE flag.
This commit allows creating a qcow2 image with an existing raw image as
its external data file while that raw image is in use by the VM.
Signed-off-by: Hanna Czenczek <hreitz@redhat.com>
---
block/qcow2.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 58 insertions(+), 5 deletions(-)
diff --git a/block/qcow2.c b/block/qcow2.c
index b601bd540d..3adc06a9ee 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -3627,6 +3627,7 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
size_t cluster_size;
int version;
int refcount_order;
+ int blk_flags;
uint64_t *refcount_table;
int ret;
uint8_t compression_type = QCOW2_COMPRESSION_TYPE_ZLIB;
@@ -3887,20 +3888,42 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
* table)
*/
options = qdict_new();
+ blk_flags = BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_NO_FLUSH;
qdict_put_str(options, "driver", "qcow2");
qdict_put_str(options, "file", bs->node_name);
if (data_bs) {
qdict_put_str(options, "data-file", data_bs->node_name);
+
+ /*
+ * If possible, suppress the WRITE permission for the data-file child.
+ * We can only do so as long as none of the operations on `blk` will
+ * write to the data file. Such writes can only happen during resize
+ * (which grows the image from length 0 to qcow2_opts->size) with data
+ * preallocation. As long as no data preallocation has been requested,
+ * we can suppress taking the WRITE permission on data-file.
+ */
+ if (qcow2_opts->preallocation == PREALLOC_MODE_METADATA ||
+ qcow2_opts->preallocation == PREALLOC_MODE_OFF) {
+ blk_flags |= BDRV_O_NO_DATA_WRITE;
+ }
+
+ /*
+ * The data-file child is only grown if too small, never shrunk. So if
+ * it already is big enough, we can suppress taking the RESIZE
+ * permission on it.
+ */
+ if (bdrv_co_getlength(data_bs) >= qcow2_opts->size) {
+ blk_flags |= BDRV_O_NO_DATA_RESIZE;
+ }
}
- blk = blk_co_new_open(NULL, NULL, options,
- BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_NO_FLUSH,
- errp);
+ blk = blk_co_new_open(NULL, NULL, options, blk_flags, errp);
if (blk == NULL) {
ret = -EIO;
goto out;
}
bdrv_graph_co_rdlock();
+ /* O_NO_DATA_WRITE note: This will not write to data-file */
ret = qcow2_alloc_clusters(blk_bs(blk), 3 * cluster_size);
if (ret < 0) {
bdrv_graph_co_rdunlock();
@@ -3919,7 +3942,10 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
s->image_data_file = g_strdup(data_bs->filename);
}
- /* Create a full header (including things like feature table) */
+ /*
+ * Create a full header (including things like feature table).
+ * O_NO_DATA_WRITE note: This will not write to data-file.
+ */
ret = qcow2_update_header(blk_bs(blk));
bdrv_graph_co_rdunlock();
@@ -3928,7 +3954,13 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
goto out;
}
- /* Okay, now that we have a valid image, let's give it the right size */
+ /*
+ * Okay, now that we have a valid image, let's give it the right size.
+ * O_NO_DATA_WRITE note: This will only write to data-file if data
+ * preallocation has been requested.
+ * O_NO_DATA_RESIZE note: We pass @exact = false, so the data-file is only
+ * resized if it is smaller than qcow2_opts->size.
+ */
ret = blk_co_truncate(blk, qcow2_opts->size, false,
qcow2_opts->preallocation, 0, errp);
if (ret < 0) {
@@ -3945,6 +3977,7 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
}
bdrv_graph_co_rdlock();
+ /* O_NO_DATA_WRITE note: This will not write to data-file */
ret = bdrv_co_change_backing_file(blk_bs(blk), qcow2_opts->backing_file,
backing_format, false);
bdrv_graph_co_rdunlock();
@@ -3960,6 +3993,7 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
/* Want encryption? There you go. */
if (qcow2_opts->encrypt) {
bdrv_graph_co_rdlock();
+ /* O_NO_DATA_WRITE note: This will not write to data-file */
ret = qcow2_set_up_encryption(blk_bs(blk), qcow2_opts->encrypt, errp);
bdrv_graph_co_rdunlock();
@@ -4401,6 +4435,15 @@ fail:
return ret;
}
+/**
+ * Resize the qcow2 image.
+ * To support BDRV_O_NO_DATA_WRITE and BDRV_O_NO_DATA_RESIZE from
+ * qcow2_co_create(), this function must:
+ * - If @exact is false, resize an external data file only if its size is less
+ * than @offset
+ * - Only write to an external data file if @prealloc prescribes data
+ * preallocation (FALLOC/FULL).
+ */
static int coroutine_fn GRAPH_RDLOCK
qcow2_co_truncate(BlockDriverState *bs, int64_t offset, bool exact,
PreallocMode prealloc, BdrvRequestFlags flags, Error **errp)
@@ -4554,6 +4597,11 @@ qcow2_co_truncate(BlockDriverState *bs, int64_t offset, bool exact,
break;
case PREALLOC_MODE_METADATA:
+ /*
+ * Note for BDRV_O_NO_DATA_RESIZE and BDRV_O_NO_DATA_WRITE: This will
+ * not write data to an external data file, and will only resize it if
+ * its current length is less than `offset`.
+ */
ret = preallocate_co(bs, old_length, offset, prealloc, errp);
if (ret < 0) {
goto fail;
@@ -4572,6 +4620,11 @@ qcow2_co_truncate(BlockDriverState *bs, int64_t offset, bool exact,
/* With a data file, preallocation means just allocating the metadata
* and forwarding the truncate request to the data file */
if (has_data_file(bs)) {
+ /*
+ * Note for BDRV_O_NO_DATA_RESIZE and BDRV_O_NO_DATA_WRITE: This
+ * *will* write data to an external data file, but only resize it
+ * if its current length is less than `offset`.
+ */
ret = preallocate_co(bs, old_length, offset, prealloc, errp);
if (ret < 0) {
goto fail;
--
2.52.0
© 2016 - 2026 Red Hat, Inc.