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
