diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2017-07-13 13:38:57 +0100 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2017-07-13 13:38:57 +0100 |
commit | 76fba746ea73c752f0168e511566f74fe4d2d32c (patch) | |
tree | 217ee45024d8927932f0e9c00197cb719d15dac7 /block/qcow2-refcount.c | |
parent | f0d2ead97cddf622a0478086886cc70a8ed6aeaf (diff) | |
parent | ced14843229cd42c282f0ee4b43bbcdc324c923a (diff) |
Merge remote-tracking branch 'remotes/maxreitz/tags/pull-block-2017-07-11' into staging
Block layer patches
# gpg: Signature made Tue 11 Jul 2017 17:05:56 BST
# gpg: using RSA key 0xF407DB0061D5CF40
# gpg: Good signature from "Max Reitz <mreitz@redhat.com>"
# Primary key fingerprint: 91BE B60A 30DB 3E88 57D1 1829 F407 DB00 61D5 CF40
* remotes/maxreitz/tags/pull-block-2017-07-11: (85 commits)
iotests: Add preallocated growth test for qcow2
iotests: Add preallocated resize test for raw
block/qcow2: falloc/full preallocating growth
block/qcow2: Rename "fail_block" to just "fail"
block/qcow2: Add qcow2_refcount_area()
block/qcow2: Metadata preallocation for truncate
block/qcow2: Lock s->lock in preallocate()
block/qcow2: Generalize preallocate()
block/file-posix: Preallocation for truncate
block/file-posix: Generalize raw_regular_truncate
block/file-posix: Extract raw_regular_truncate()
block/file-posix: Small fixes in raw_create()
qemu-img: Expose PreallocMode for resizing
block: Add PreallocMode to blk_truncate()
block: Add PreallocMode to bdrv_truncate()
block: Add PreallocMode to BD.bdrv_truncate()
iotests: add test 178 for qemu-img measure
qemu-iotests: support per-format golden output files
qemu-img: add measure subcommand
qcow2: add bdrv_measure() support
...
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'block/qcow2-refcount.c')
-rw-r--r-- | block/qcow2-refcount.c | 346 |
1 files changed, 230 insertions, 116 deletions
diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c index 7c06061aae..c9b0dcb4f3 100644 --- a/block/qcow2-refcount.c +++ b/block/qcow2-refcount.c @@ -281,25 +281,6 @@ int qcow2_get_refcount(BlockDriverState *bs, int64_t cluster_index, return 0; } -/* - * Rounds the refcount table size up to avoid growing the table for each single - * refcount block that is allocated. - */ -static unsigned int next_refcount_table_size(BDRVQcow2State *s, - unsigned int min_size) -{ - unsigned int min_clusters = (min_size >> (s->cluster_bits - 3)) + 1; - unsigned int refcount_table_clusters = - MAX(1, s->refcount_table_size >> (s->cluster_bits - 3)); - - while (min_clusters > refcount_table_clusters) { - refcount_table_clusters = (refcount_table_clusters * 3 + 1) / 2; - } - - return refcount_table_clusters << (s->cluster_bits - 3); -} - - /* Checks if two offsets are described by the same refcount block */ static int in_same_refcount_block(BDRVQcow2State *s, uint64_t offset_a, uint64_t offset_b) @@ -321,7 +302,7 @@ static int alloc_refcount_block(BlockDriverState *bs, { BDRVQcow2State *s = bs->opaque; unsigned int refcount_table_index; - int ret; + int64_t ret; BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_ALLOC); @@ -396,7 +377,7 @@ static int alloc_refcount_block(BlockDriverState *bs, ret = qcow2_cache_get_empty(bs, s->refcount_block_cache, new_block, refcount_block); if (ret < 0) { - goto fail_block; + goto fail; } memset(*refcount_block, 0, s->cluster_size); @@ -411,12 +392,12 @@ static int alloc_refcount_block(BlockDriverState *bs, ret = update_refcount(bs, new_block, s->cluster_size, 1, false, QCOW2_DISCARD_NEVER); if (ret < 0) { - goto fail_block; + goto fail; } ret = qcow2_cache_flush(bs, s->refcount_block_cache); if (ret < 0) { - goto fail_block; + goto fail; } /* Initialize the new refcount block only after updating its refcount, @@ -424,7 +405,7 @@ static int alloc_refcount_block(BlockDriverState *bs, ret = qcow2_cache_get_empty(bs, s->refcount_block_cache, new_block, refcount_block); if (ret < 0) { - goto fail_block; + goto fail; } memset(*refcount_block, 0, s->cluster_size); @@ -435,7 +416,7 @@ static int alloc_refcount_block(BlockDriverState *bs, qcow2_cache_entry_mark_dirty(bs, s->refcount_block_cache, *refcount_block); ret = qcow2_cache_flush(bs, s->refcount_block_cache); if (ret < 0) { - goto fail_block; + goto fail; } /* If the refcount table is big enough, just hook the block up there */ @@ -446,7 +427,7 @@ static int alloc_refcount_block(BlockDriverState *bs, s->refcount_table_offset + refcount_table_index * sizeof(uint64_t), &data64, sizeof(data64)); if (ret < 0) { - goto fail_block; + goto fail; } s->refcount_table[refcount_table_index] = new_block; @@ -490,74 +471,201 @@ static int alloc_refcount_block(BlockDriverState *bs, (new_block >> s->cluster_bits) + 1), s->refcount_block_size); - if (blocks_used > QCOW_MAX_REFTABLE_SIZE / sizeof(uint64_t)) { - return -EFBIG; + /* Create the new refcount table and blocks */ + uint64_t meta_offset = (blocks_used * s->refcount_block_size) * + s->cluster_size; + + ret = qcow2_refcount_area(bs, meta_offset, 0, false, + refcount_table_index, new_block); + if (ret < 0) { + return ret; } - /* And now we need at least one block more for the new metadata */ - uint64_t table_size = next_refcount_table_size(s, blocks_used + 1); - uint64_t last_table_size; - uint64_t blocks_clusters; - do { - uint64_t table_clusters = - size_to_clusters(s, table_size * sizeof(uint64_t)); - blocks_clusters = 1 + - DIV_ROUND_UP(table_clusters, s->refcount_block_size); - uint64_t meta_clusters = table_clusters + blocks_clusters; + ret = load_refcount_block(bs, new_block, refcount_block); + if (ret < 0) { + return ret; + } - last_table_size = table_size; - table_size = next_refcount_table_size(s, blocks_used + - DIV_ROUND_UP(meta_clusters, s->refcount_block_size)); + /* If we were trying to do the initial refcount update for some cluster + * allocation, we might have used the same clusters to store newly + * allocated metadata. Make the caller search some new space. */ + return -EAGAIN; - } while (last_table_size != table_size); +fail: + if (*refcount_block != NULL) { + qcow2_cache_put(bs, s->refcount_block_cache, refcount_block); + } + return ret; +} -#ifdef DEBUG_ALLOC2 - fprintf(stderr, "qcow2: Grow refcount table %" PRId32 " => %" PRId64 "\n", - s->refcount_table_size, table_size); -#endif +/* + * Starting at @start_offset, this function creates new self-covering refcount + * structures: A new refcount table and refcount blocks which cover all of + * themselves, and a number of @additional_clusters beyond their end. + * @start_offset must be at the end of the image file, that is, there must be + * only empty space beyond it. + * If @exact_size is false, the refcount table will have 50 % more entries than + * necessary so it will not need to grow again soon. + * If @new_refblock_offset is not zero, it contains the offset of a refcount + * block that should be entered into the new refcount table at index + * @new_refblock_index. + * + * Returns: The offset after the new refcount structures (i.e. where the + * @additional_clusters may be placed) on success, -errno on error. + */ +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; + int total_refblock_count, table_size, area_reftable_index, table_clusters; + int i; + uint64_t table_offset, block_offset, end_offset; + int ret; + uint64_t *new_table; - /* Create the new refcount table and blocks */ - uint64_t meta_offset = (blocks_used * s->refcount_block_size) * - s->cluster_size; - uint64_t table_offset = meta_offset + blocks_clusters * s->cluster_size; - uint64_t *new_table = g_try_new0(uint64_t, table_size); - void *new_blocks = g_try_malloc0(blocks_clusters * s->cluster_size); + assert(!(start_offset % s->cluster_size)); + + qcow2_refcount_metadata_size(start_offset / s->cluster_size + + additional_clusters, + s->cluster_size, s->refcount_order, + !exact_size, &total_refblock_count_u64); + if (total_refblock_count_u64 > QCOW_MAX_REFTABLE_SIZE) { + return -EFBIG; + } + total_refblock_count = total_refblock_count_u64; + + /* Index in the refcount table of the first refcount block to cover the area + * of refcount structures we are about to create; we know that + * @total_refblock_count can cover @start_offset, so this will definitely + * fit into an int. */ + area_reftable_index = (start_offset / s->cluster_size) / + s->refcount_block_size; - assert(table_size > 0 && blocks_clusters > 0); - if (new_table == NULL || new_blocks == NULL) { + if (exact_size) { + table_size = total_refblock_count; + } else { + table_size = total_refblock_count + + DIV_ROUND_UP(total_refblock_count, 2); + } + /* The qcow2 file can only store the reftable size in number of clusters */ + table_size = ROUND_UP(table_size, s->cluster_size / sizeof(uint64_t)); + table_clusters = (table_size * sizeof(uint64_t)) / s->cluster_size; + + if (table_size > QCOW_MAX_REFTABLE_SIZE) { + return -EFBIG; + } + + new_table = g_try_new0(uint64_t, table_size); + + assert(table_size > 0); + if (new_table == NULL) { ret = -ENOMEM; - goto fail_table; + goto fail; } /* Fill the new refcount table */ - memcpy(new_table, s->refcount_table, - s->refcount_table_size * sizeof(uint64_t)); - new_table[refcount_table_index] = new_block; + if (table_size > s->max_refcount_table_index) { + /* We're actually growing the reftable */ + memcpy(new_table, s->refcount_table, + (s->max_refcount_table_index + 1) * sizeof(uint64_t)); + } else { + /* Improbable case: We're shrinking the reftable. However, the caller + * has assured us that there is only empty space beyond @start_offset, + * so we can simply drop all of the refblocks that won't fit into the + * new reftable. */ + memcpy(new_table, s->refcount_table, table_size * sizeof(uint64_t)); + } - int i; - for (i = 0; i < blocks_clusters; i++) { - new_table[blocks_used + i] = meta_offset + (i * s->cluster_size); + if (new_refblock_offset) { + assert(new_refblock_index < total_refblock_count); + new_table[new_refblock_index] = new_refblock_offset; } - /* Fill the refcount blocks */ - uint64_t table_clusters = size_to_clusters(s, table_size * sizeof(uint64_t)); - int block = 0; - for (i = 0; i < table_clusters + blocks_clusters; i++) { - s->set_refcount(new_blocks, block++, 1); + /* Count how many new refblocks we have to create */ + additional_refblock_count = 0; + for (i = area_reftable_index; i < total_refblock_count; i++) { + if (!new_table[i]) { + additional_refblock_count++; + } + } + + table_offset = start_offset + additional_refblock_count * s->cluster_size; + end_offset = table_offset + table_clusters * s->cluster_size; + + /* Fill the refcount blocks, and create new ones, if necessary */ + block_offset = start_offset; + for (i = area_reftable_index; i < total_refblock_count; i++) { + void *refblock_data; + uint64_t first_offset_covered; + + /* Reuse an existing refblock if possible, create a new one otherwise */ + if (new_table[i]) { + ret = qcow2_cache_get(bs, s->refcount_block_cache, new_table[i], + &refblock_data); + if (ret < 0) { + goto fail; + } + } else { + ret = qcow2_cache_get_empty(bs, s->refcount_block_cache, + block_offset, &refblock_data); + if (ret < 0) { + goto fail; + } + memset(refblock_data, 0, s->cluster_size); + qcow2_cache_entry_mark_dirty(bs, s->refcount_block_cache, + refblock_data); + + new_table[i] = block_offset; + block_offset += s->cluster_size; + } + + /* First host offset covered by this refblock */ + first_offset_covered = (uint64_t)i * s->refcount_block_size * + s->cluster_size; + if (first_offset_covered < end_offset) { + int j, end_index; + + /* Set the refcount of all of the new refcount structures to 1 */ + + if (first_offset_covered < start_offset) { + assert(i == area_reftable_index); + j = (start_offset - first_offset_covered) / s->cluster_size; + assert(j < s->refcount_block_size); + } else { + j = 0; + } + + end_index = MIN((end_offset - first_offset_covered) / + s->cluster_size, + s->refcount_block_size); + + for (; j < end_index; j++) { + /* The caller guaranteed us this space would be empty */ + assert(s->get_refcount(refblock_data, j) == 0); + s->set_refcount(refblock_data, j, 1); + } + + qcow2_cache_entry_mark_dirty(bs, s->refcount_block_cache, + refblock_data); + } + + qcow2_cache_put(bs, s->refcount_block_cache, &refblock_data); } + assert(block_offset == table_offset); + /* Write refcount blocks to disk */ BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_ALLOC_WRITE_BLOCKS); - ret = bdrv_pwrite_sync(bs->file, meta_offset, new_blocks, - blocks_clusters * s->cluster_size); - g_free(new_blocks); - new_blocks = NULL; + ret = qcow2_cache_flush(bs, s->refcount_block_cache); if (ret < 0) { - goto fail_table; + goto fail; } /* Write refcount table to disk */ - for(i = 0; i < table_size; i++) { + for (i = 0; i < total_refblock_count; i++) { cpu_to_be64s(&new_table[i]); } @@ -565,10 +673,10 @@ static int alloc_refcount_block(BlockDriverState *bs, ret = bdrv_pwrite_sync(bs->file, table_offset, new_table, table_size * sizeof(uint64_t)); if (ret < 0) { - goto fail_table; + goto fail; } - for(i = 0; i < table_size; i++) { + for (i = 0; i < total_refblock_count; i++) { be64_to_cpus(&new_table[i]); } @@ -584,7 +692,7 @@ static int alloc_refcount_block(BlockDriverState *bs, offsetof(QCowHeader, refcount_table_offset), &data, sizeof(data)); if (ret < 0) { - goto fail_table; + goto fail; } /* And switch it in memory */ @@ -601,23 +709,10 @@ static int alloc_refcount_block(BlockDriverState *bs, qcow2_free_clusters(bs, old_table_offset, old_table_size * sizeof(uint64_t), QCOW2_DISCARD_OTHER); - ret = load_refcount_block(bs, new_block, refcount_block); - if (ret < 0) { - return ret; - } + return end_offset; - /* If we were trying to do the initial refcount update for some cluster - * allocation, we might have used the same clusters to store newly - * allocated metadata. Make the caller search some new space. */ - return -EAGAIN; - -fail_table: - g_free(new_blocks); +fail: g_free(new_table); -fail_block: - if (*refcount_block != NULL) { - qcow2_cache_put(bs, s->refcount_block_cache, refcount_block); - } return ret; } @@ -1323,11 +1418,10 @@ static int realloc_refcount_array(BDRVQcow2State *s, void **array, * * Modifies the number of errors in res. */ -static int inc_refcounts(BlockDriverState *bs, - BdrvCheckResult *res, - void **refcount_table, - int64_t *refcount_table_size, - int64_t offset, int64_t size) +int qcow2_inc_refcounts_imrt(BlockDriverState *bs, BdrvCheckResult *res, + void **refcount_table, + int64_t *refcount_table_size, + int64_t offset, int64_t size) { BDRVQcow2State *s = bs->opaque; uint64_t start, last, cluster_offset, k, refcount; @@ -1420,8 +1514,9 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res, nb_csectors = ((l2_entry >> s->csize_shift) & s->csize_mask) + 1; l2_entry &= s->cluster_offset_mask; - ret = inc_refcounts(bs, res, refcount_table, refcount_table_size, - l2_entry & ~511, nb_csectors * 512); + ret = qcow2_inc_refcounts_imrt(bs, res, + refcount_table, refcount_table_size, + l2_entry & ~511, nb_csectors * 512); if (ret < 0) { goto fail; } @@ -1454,8 +1549,9 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res, } /* Mark cluster as used */ - ret = inc_refcounts(bs, res, refcount_table, refcount_table_size, - offset, s->cluster_size); + ret = qcow2_inc_refcounts_imrt(bs, res, + refcount_table, refcount_table_size, + offset, s->cluster_size); if (ret < 0) { goto fail; } @@ -1508,8 +1604,8 @@ static int check_refcounts_l1(BlockDriverState *bs, l1_size2 = l1_size * sizeof(uint64_t); /* Mark L1 table as used */ - ret = inc_refcounts(bs, res, refcount_table, refcount_table_size, - l1_table_offset, l1_size2); + ret = qcow2_inc_refcounts_imrt(bs, res, refcount_table, refcount_table_size, + l1_table_offset, l1_size2); if (ret < 0) { goto fail; } @@ -1538,8 +1634,9 @@ static int check_refcounts_l1(BlockDriverState *bs, if (l2_offset) { /* Mark L2 table as used */ l2_offset &= L1E_OFFSET_MASK; - ret = inc_refcounts(bs, res, refcount_table, refcount_table_size, - l2_offset, s->cluster_size); + ret = qcow2_inc_refcounts_imrt(bs, res, + refcount_table, refcount_table_size, + l2_offset, s->cluster_size); if (ret < 0) { goto fail; } @@ -1730,7 +1827,7 @@ static int check_refblocks(BlockDriverState *bs, BdrvCheckResult *res, } ret = bdrv_truncate(bs->file, offset + s->cluster_size, - &local_err); + PREALLOC_MODE_OFF, &local_err); if (ret < 0) { error_report_err(local_err); goto resize_fail; @@ -1757,14 +1854,15 @@ static int check_refblocks(BlockDriverState *bs, BdrvCheckResult *res, } res->corruptions_fixed++; - ret = inc_refcounts(bs, res, refcount_table, nb_clusters, - offset, s->cluster_size); + ret = qcow2_inc_refcounts_imrt(bs, res, + refcount_table, nb_clusters, + offset, s->cluster_size); if (ret < 0) { return ret; } /* No need to check whether the refcount is now greater than 1: * This area was just allocated and zeroed, so it can only be - * exactly 1 after inc_refcounts() */ + * exactly 1 after qcow2_inc_refcounts_imrt() */ continue; resize_fail: @@ -1779,8 +1877,8 @@ resize_fail: } if (offset != 0) { - ret = inc_refcounts(bs, res, refcount_table, nb_clusters, - offset, s->cluster_size); + ret = qcow2_inc_refcounts_imrt(bs, res, refcount_table, nb_clusters, + offset, s->cluster_size); if (ret < 0) { return ret; } @@ -1820,8 +1918,8 @@ static int calculate_refcounts(BlockDriverState *bs, BdrvCheckResult *res, } /* header */ - ret = inc_refcounts(bs, res, refcount_table, nb_clusters, - 0, s->cluster_size); + ret = qcow2_inc_refcounts_imrt(bs, res, refcount_table, nb_clusters, + 0, s->cluster_size); if (ret < 0) { return ret; } @@ -1842,16 +1940,32 @@ static int calculate_refcounts(BlockDriverState *bs, BdrvCheckResult *res, return ret; } } - ret = inc_refcounts(bs, res, refcount_table, nb_clusters, - s->snapshots_offset, s->snapshots_size); + ret = qcow2_inc_refcounts_imrt(bs, res, refcount_table, nb_clusters, + s->snapshots_offset, s->snapshots_size); if (ret < 0) { return ret; } /* refcount data */ - ret = inc_refcounts(bs, res, refcount_table, nb_clusters, - s->refcount_table_offset, - s->refcount_table_size * sizeof(uint64_t)); + ret = qcow2_inc_refcounts_imrt(bs, res, refcount_table, nb_clusters, + s->refcount_table_offset, + s->refcount_table_size * sizeof(uint64_t)); + if (ret < 0) { + return ret; + } + + /* encryption */ + if (s->crypto_header.length) { + ret = qcow2_inc_refcounts_imrt(bs, res, refcount_table, nb_clusters, + s->crypto_header.offset, + s->crypto_header.length); + if (ret < 0) { + return ret; + } + } + + /* bitmaps */ + ret = qcow2_check_bitmaps_refcounts(bs, res, refcount_table, nb_clusters); if (ret < 0) { return ret; } |