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 <[email protected]>
---
 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


Reply via email to