diff mbox

[v3,14/16] block/qcow2: falloc/full preallocating growth

Message ID 20170526165518.7580-15-mreitz@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Max Reitz May 26, 2017, 4:55 p.m. UTC
Implement the preallocation modes falloc and full for growing qcow2
images.

Signed-off-by: Max Reitz <mreitz@redhat.com>
---
 block/qcow2.h          |   5 +++
 block/qcow2-refcount.c |  12 ++----
 block/qcow2.c          | 100 ++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 108 insertions(+), 9 deletions(-)

Comments

Stefan Hajnoczi May 31, 2017, 10:41 a.m. UTC | #1
On Fri, May 26, 2017 at 06:55:16PM +0200, Max Reitz wrote:
> @@ -2677,6 +2679,102 @@ static int qcow2_truncate(BlockDriverState *bs, int64_t offset,
>          }
>          break;
>  
> +    case PREALLOC_MODE_FALLOC:
> +    case PREALLOC_MODE_FULL:
> +    {
> +        int64_t allocation_start, host_offset, guest_offset;
> +        int64_t clusters_allocated;
> +        int64_t old_file_size, new_file_size;
> +        uint64_t nb_new_data_clusters, nb_new_l2_tables;
> +
> +        old_file_size = bdrv_getlength(bs->file->bs);
> +        if (old_file_size < 0) {
> +            error_setg_errno(errp, -old_file_size,
> +                             "Failed to inquire current file length");
> +            return ret;
> +        }
> +
> +        nb_new_data_clusters = DIV_ROUND_UP(offset - old_length,
> +                                            s->cluster_size);
> +
> +        /* This is an overestimation; we will not actually allocate space for
> +         * these in the file but just make sure the new refcount structures are
> +         * able to cover them so we will not have to allocate new refblocks
> +         * while entering the data blocks in the potentially new L2 tables.
> +         * (We do not actually care where the L2 tables are placed. Maybe they
> +         *  are already allocated or they can be placed somewhere before
> +         *  @old_file_size. It does not matter because they will be fully
> +         *  allocated automatically, so they do not need to be covered by the
> +         *  preallocation. All that matters is that we will not have to allocate
> +         *  new refcount structures for them.) */
> +        nb_new_l2_tables = DIV_ROUND_UP(nb_new_data_clusters,
> +                                        s->cluster_size / sizeof(uint64_t));
> +        /* The cluster range may not be aligned to L2 boundaries, so add one L2
> +         * table for a potential head/tail */
> +        nb_new_l2_tables++;
> +
> +        allocation_start = qcow2_refcount_area(bs, old_file_size,
> +                                               nb_new_data_clusters +
> +                                               nb_new_l2_tables,
> +                                               true, 0, 0);
> +        if (allocation_start < 0) {
> +            error_setg_errno(errp, -allocation_start,
> +                             "Failed to resize refcount structures");
> +            return -allocation_start;
> +        }
> +
> +        clusters_allocated = qcow2_alloc_clusters_at(bs, allocation_start,
> +                                                     nb_new_data_clusters);
> +        if (clusters_allocated < 0) {
> +            error_setg_errno(errp, -clusters_allocated,
> +                             "Failed to allocate data clusters");
> +            return -clusters_allocated;
> +        }
> +
> +        assert(clusters_allocated == nb_new_data_clusters);
> +
> +        /* Allocate the data area */
> +        new_file_size = allocation_start +
> +                        nb_new_data_clusters * s->cluster_size;
> +        ret = bdrv_truncate(bs->file, new_file_size, prealloc, errp);
> +        if (ret < 0) {
> +            error_prepend(errp, "Failed to resize underlying file: ");
> +            qcow2_free_clusters(bs, allocation_start,
> +                                nb_new_data_clusters * s->cluster_size,
> +                                QCOW2_DISCARD_OTHER);
> +            return ret;
> +        }
> +
> +        /* Create the necessary L2 entries */
> +        host_offset = allocation_start;
> +        guest_offset = old_length;
> +        while (nb_new_data_clusters) {
> +            int64_t guest_cluster = guest_offset >> s->cluster_bits;
> +            int64_t nb_clusters = MIN(nb_new_data_clusters,
> +                                      s->l2_size - guest_cluster % s->l2_size);
> +            QCowL2Meta allocation = {
> +                .offset       = guest_offset,
> +                .alloc_offset = host_offset,
> +                .nb_clusters  = nb_clusters,
> +            };
> +            qemu_co_queue_init(&allocation.dependent_requests);
> +
> +            ret = qcow2_alloc_cluster_link_l2(bs, &allocation);
> +            if (ret < 0) {
> +                error_setg_errno(errp, -ret, "Failed to update L2 tables");
> +                qcow2_free_clusters(bs, host_offset,
> +                                    nb_new_data_clusters * s->cluster_size,
> +                                    QCOW2_DISCARD_OTHER);
> +                return ret;
> +            }
> +
> +            guest_offset += nb_clusters * s->cluster_size;
> +            host_offset += nb_clusters * s->cluster_size;
> +            nb_new_data_clusters -= nb_clusters;
> +        }
> +        break;
> +    }

Flush L1/L2 tables?
Max Reitz May 31, 2017, 12:19 p.m. UTC | #2
On 2017-05-31 12:41, Stefan Hajnoczi wrote:
> On Fri, May 26, 2017 at 06:55:16PM +0200, Max Reitz wrote:
>> @@ -2677,6 +2679,102 @@ static int qcow2_truncate(BlockDriverState *bs, int64_t offset,
>>          }
>>          break;
>>  
>> +    case PREALLOC_MODE_FALLOC:
>> +    case PREALLOC_MODE_FULL:
>> +    {
>> +        int64_t allocation_start, host_offset, guest_offset;
>> +        int64_t clusters_allocated;
>> +        int64_t old_file_size, new_file_size;
>> +        uint64_t nb_new_data_clusters, nb_new_l2_tables;
>> +
>> +        old_file_size = bdrv_getlength(bs->file->bs);
>> +        if (old_file_size < 0) {
>> +            error_setg_errno(errp, -old_file_size,
>> +                             "Failed to inquire current file length");
>> +            return ret;
>> +        }
>> +
>> +        nb_new_data_clusters = DIV_ROUND_UP(offset - old_length,
>> +                                            s->cluster_size);
>> +
>> +        /* This is an overestimation; we will not actually allocate space for
>> +         * these in the file but just make sure the new refcount structures are
>> +         * able to cover them so we will not have to allocate new refblocks
>> +         * while entering the data blocks in the potentially new L2 tables.
>> +         * (We do not actually care where the L2 tables are placed. Maybe they
>> +         *  are already allocated or they can be placed somewhere before
>> +         *  @old_file_size. It does not matter because they will be fully
>> +         *  allocated automatically, so they do not need to be covered by the
>> +         *  preallocation. All that matters is that we will not have to allocate
>> +         *  new refcount structures for them.) */
>> +        nb_new_l2_tables = DIV_ROUND_UP(nb_new_data_clusters,
>> +                                        s->cluster_size / sizeof(uint64_t));
>> +        /* The cluster range may not be aligned to L2 boundaries, so add one L2
>> +         * table for a potential head/tail */
>> +        nb_new_l2_tables++;
>> +
>> +        allocation_start = qcow2_refcount_area(bs, old_file_size,
>> +                                               nb_new_data_clusters +
>> +                                               nb_new_l2_tables,
>> +                                               true, 0, 0);
>> +        if (allocation_start < 0) {
>> +            error_setg_errno(errp, -allocation_start,
>> +                             "Failed to resize refcount structures");
>> +            return -allocation_start;
>> +        }
>> +
>> +        clusters_allocated = qcow2_alloc_clusters_at(bs, allocation_start,
>> +                                                     nb_new_data_clusters);
>> +        if (clusters_allocated < 0) {
>> +            error_setg_errno(errp, -clusters_allocated,
>> +                             "Failed to allocate data clusters");
>> +            return -clusters_allocated;
>> +        }
>> +
>> +        assert(clusters_allocated == nb_new_data_clusters);
>> +
>> +        /* Allocate the data area */
>> +        new_file_size = allocation_start +
>> +                        nb_new_data_clusters * s->cluster_size;
>> +        ret = bdrv_truncate(bs->file, new_file_size, prealloc, errp);
>> +        if (ret < 0) {
>> +            error_prepend(errp, "Failed to resize underlying file: ");
>> +            qcow2_free_clusters(bs, allocation_start,
>> +                                nb_new_data_clusters * s->cluster_size,
>> +                                QCOW2_DISCARD_OTHER);
>> +            return ret;
>> +        }
>> +
>> +        /* Create the necessary L2 entries */
>> +        host_offset = allocation_start;
>> +        guest_offset = old_length;
>> +        while (nb_new_data_clusters) {
>> +            int64_t guest_cluster = guest_offset >> s->cluster_bits;
>> +            int64_t nb_clusters = MIN(nb_new_data_clusters,
>> +                                      s->l2_size - guest_cluster % s->l2_size);
>> +            QCowL2Meta allocation = {
>> +                .offset       = guest_offset,
>> +                .alloc_offset = host_offset,
>> +                .nb_clusters  = nb_clusters,
>> +            };
>> +            qemu_co_queue_init(&allocation.dependent_requests);
>> +
>> +            ret = qcow2_alloc_cluster_link_l2(bs, &allocation);
>> +            if (ret < 0) {
>> +                error_setg_errno(errp, -ret, "Failed to update L2 tables");
>> +                qcow2_free_clusters(bs, host_offset,
>> +                                    nb_new_data_clusters * s->cluster_size,
>> +                                    QCOW2_DISCARD_OTHER);
>> +                return ret;
>> +            }
>> +
>> +            guest_offset += nb_clusters * s->cluster_size;
>> +            host_offset += nb_clusters * s->cluster_size;
>> +            nb_new_data_clusters -= nb_clusters;
>> +        }
>> +        break;
>> +    }
> 
> Flush L1/L2 tables?

Right. It would be good to actually have the area allocated for sure
before writing the new size. :-)

The same applies to the metadata-only preallocate(), though. So this
should probably be added in patch 11.

Thanks for spotting this, and thanks for reviewing!

Max
diff mbox

Patch

diff --git a/block/qcow2.h b/block/qcow2.h
index c216bf4..cf33e4e 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -509,6 +509,11 @@  int qcow2_update_cluster_refcount(BlockDriverState *bs, int64_t cluster_index,
                                   uint64_t addend, bool decrease,
                                   enum qcow2_discard_type type);
 
+int64_t qcow2_refcount_area(BlockDriverState *bs, uint64_t offset,
+                            uint64_t additional_clusters, bool exact_size,
+                            int new_refblock_index,
+                            uint64_t new_refblock_offset);
+
 int64_t qcow2_alloc_clusters(BlockDriverState *bs, uint64_t size);
 int64_t qcow2_alloc_clusters_at(BlockDriverState *bs, uint64_t offset,
                                 int64_t nb_clusters);
diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c
index 9d109e9..fcaa7ac 100644
--- a/block/qcow2-refcount.c
+++ b/block/qcow2-refcount.c
@@ -34,10 +34,6 @@  static int64_t alloc_clusters_noref(BlockDriverState *bs, uint64_t size);
 static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
                             int64_t offset, int64_t length, uint64_t addend,
                             bool decrease, enum qcow2_discard_type type);
-static int64_t qcow2_refcount_area(BlockDriverState *bs, uint64_t offset,
-                                   uint64_t additional_clusters,
-                                   bool exact_size, int new_refblock_index,
-                                   uint64_t new_refblock_offset);
 
 static uint64_t get_refcount_ro0(const void *refcount_array, uint64_t index);
 static uint64_t get_refcount_ro1(const void *refcount_array, uint64_t index);
@@ -517,10 +513,10 @@  fail:
  * Returns: The offset after the new refcount structures (i.e. where the
  *          @additional_clusters may be placed) on success, -errno on error.
  */
-static int64_t qcow2_refcount_area(BlockDriverState *bs, uint64_t start_offset,
-                                   uint64_t additional_clusters,
-                                   bool exact_size, int new_refblock_index,
-                                   uint64_t new_refblock_offset)
+int64_t qcow2_refcount_area(BlockDriverState *bs, uint64_t start_offset,
+                            uint64_t additional_clusters, bool exact_size,
+                            int new_refblock_index,
+                            uint64_t new_refblock_offset)
 {
     BDRVQcow2State *s = bs->opaque;
     uint64_t total_refblock_count_u64, additional_refblock_count;
diff --git a/block/qcow2.c b/block/qcow2.c
index 86497c0..854f5e5 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -2632,7 +2632,9 @@  static int qcow2_truncate(BlockDriverState *bs, int64_t offset,
     int64_t new_l1_size;
     int ret;
 
-    if (prealloc != PREALLOC_MODE_OFF && prealloc != PREALLOC_MODE_METADATA) {
+    if (prealloc != PREALLOC_MODE_OFF && prealloc != PREALLOC_MODE_METADATA &&
+        prealloc != PREALLOC_MODE_FALLOC && prealloc != PREALLOC_MODE_FULL)
+    {
         error_setg(errp, "Unsupported preallocation mode '%s'",
                    PreallocMode_lookup[prealloc]);
         return -ENOTSUP;
@@ -2677,6 +2679,102 @@  static int qcow2_truncate(BlockDriverState *bs, int64_t offset,
         }
         break;
 
+    case PREALLOC_MODE_FALLOC:
+    case PREALLOC_MODE_FULL:
+    {
+        int64_t allocation_start, host_offset, guest_offset;
+        int64_t clusters_allocated;
+        int64_t old_file_size, new_file_size;
+        uint64_t nb_new_data_clusters, nb_new_l2_tables;
+
+        old_file_size = bdrv_getlength(bs->file->bs);
+        if (old_file_size < 0) {
+            error_setg_errno(errp, -old_file_size,
+                             "Failed to inquire current file length");
+            return ret;
+        }
+
+        nb_new_data_clusters = DIV_ROUND_UP(offset - old_length,
+                                            s->cluster_size);
+
+        /* This is an overestimation; we will not actually allocate space for
+         * these in the file but just make sure the new refcount structures are
+         * able to cover them so we will not have to allocate new refblocks
+         * while entering the data blocks in the potentially new L2 tables.
+         * (We do not actually care where the L2 tables are placed. Maybe they
+         *  are already allocated or they can be placed somewhere before
+         *  @old_file_size. It does not matter because they will be fully
+         *  allocated automatically, so they do not need to be covered by the
+         *  preallocation. All that matters is that we will not have to allocate
+         *  new refcount structures for them.) */
+        nb_new_l2_tables = DIV_ROUND_UP(nb_new_data_clusters,
+                                        s->cluster_size / sizeof(uint64_t));
+        /* The cluster range may not be aligned to L2 boundaries, so add one L2
+         * table for a potential head/tail */
+        nb_new_l2_tables++;
+
+        allocation_start = qcow2_refcount_area(bs, old_file_size,
+                                               nb_new_data_clusters +
+                                               nb_new_l2_tables,
+                                               true, 0, 0);
+        if (allocation_start < 0) {
+            error_setg_errno(errp, -allocation_start,
+                             "Failed to resize refcount structures");
+            return -allocation_start;
+        }
+
+        clusters_allocated = qcow2_alloc_clusters_at(bs, allocation_start,
+                                                     nb_new_data_clusters);
+        if (clusters_allocated < 0) {
+            error_setg_errno(errp, -clusters_allocated,
+                             "Failed to allocate data clusters");
+            return -clusters_allocated;
+        }
+
+        assert(clusters_allocated == nb_new_data_clusters);
+
+        /* Allocate the data area */
+        new_file_size = allocation_start +
+                        nb_new_data_clusters * s->cluster_size;
+        ret = bdrv_truncate(bs->file, new_file_size, prealloc, errp);
+        if (ret < 0) {
+            error_prepend(errp, "Failed to resize underlying file: ");
+            qcow2_free_clusters(bs, allocation_start,
+                                nb_new_data_clusters * s->cluster_size,
+                                QCOW2_DISCARD_OTHER);
+            return ret;
+        }
+
+        /* Create the necessary L2 entries */
+        host_offset = allocation_start;
+        guest_offset = old_length;
+        while (nb_new_data_clusters) {
+            int64_t guest_cluster = guest_offset >> s->cluster_bits;
+            int64_t nb_clusters = MIN(nb_new_data_clusters,
+                                      s->l2_size - guest_cluster % s->l2_size);
+            QCowL2Meta allocation = {
+                .offset       = guest_offset,
+                .alloc_offset = host_offset,
+                .nb_clusters  = nb_clusters,
+            };
+            qemu_co_queue_init(&allocation.dependent_requests);
+
+            ret = qcow2_alloc_cluster_link_l2(bs, &allocation);
+            if (ret < 0) {
+                error_setg_errno(errp, -ret, "Failed to update L2 tables");
+                qcow2_free_clusters(bs, host_offset,
+                                    nb_new_data_clusters * s->cluster_size,
+                                    QCOW2_DISCARD_OTHER);
+                return ret;
+            }
+
+            guest_offset += nb_clusters * s->cluster_size;
+            host_offset += nb_clusters * s->cluster_size;
+            nb_new_data_clusters -= nb_clusters;
+        }
+        break;
+    }
+
     default:
         g_assert_not_reached();
     }