Message ID | 20170526165518.7580-15-mreitz@redhat.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
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?
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 --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(); }
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(-)