qcow2: Helper function for refcount modification
Since refcounts do not always have to be a uint16_t, all refcount blocks and arrays in memory should not have a specific type (thus they become pointers to void) and for accessing them, two helper functions are used (a getter and a setter). Those functions are called indirectly through function pointers in the BDRVQcowState so they may later be exchanged for different refcount orders. With the check and repair functions using this function, the refcount array they are creating will be in big endian byte order; additionally, using realloc_refcount_array() makes the size of this refcount array always cluster-aligned. Both combined allow rebuild_refcount_structure() to drop the bounce buffer which was used to convert parts of the refcount array to big endian byte order and store them on disk. Instead, those parts can now be written directly. [ kwolf: Fixed a build failure on 32 bit and another with old glib ] Signed-off-by: Max Reitz <mreitz@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
This commit is contained in:
parent
5fee192efd
commit
7453c96b78
@ -32,6 +32,11 @@ 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 uint64_t get_refcount_ro4(const void *refcount_array, uint64_t index);
|
||||
|
||||
static void set_refcount_ro4(void *refcount_array, uint64_t index,
|
||||
uint64_t value);
|
||||
|
||||
|
||||
/*********************************************************/
|
||||
/* refcount handling */
|
||||
@ -42,6 +47,9 @@ int qcow2_refcount_init(BlockDriverState *bs)
|
||||
unsigned int refcount_table_size2, i;
|
||||
int ret;
|
||||
|
||||
s->get_refcount = &get_refcount_ro4;
|
||||
s->set_refcount = &set_refcount_ro4;
|
||||
|
||||
assert(s->refcount_table_size <= INT_MAX / sizeof(uint64_t));
|
||||
refcount_table_size2 = s->refcount_table_size * sizeof(uint64_t);
|
||||
s->refcount_table = g_try_malloc(refcount_table_size2);
|
||||
@ -72,6 +80,19 @@ void qcow2_refcount_close(BlockDriverState *bs)
|
||||
}
|
||||
|
||||
|
||||
static uint64_t get_refcount_ro4(const void *refcount_array, uint64_t index)
|
||||
{
|
||||
return be16_to_cpu(((const uint16_t *)refcount_array)[index]);
|
||||
}
|
||||
|
||||
static void set_refcount_ro4(void *refcount_array, uint64_t index,
|
||||
uint64_t value)
|
||||
{
|
||||
assert(!(value >> 16));
|
||||
((uint16_t *)refcount_array)[index] = cpu_to_be16(value);
|
||||
}
|
||||
|
||||
|
||||
static int load_refcount_block(BlockDriverState *bs,
|
||||
int64_t refcount_block_offset,
|
||||
void **refcount_block)
|
||||
@ -97,7 +118,7 @@ int qcow2_get_refcount(BlockDriverState *bs, int64_t cluster_index,
|
||||
uint64_t refcount_table_index, block_index;
|
||||
int64_t refcount_block_offset;
|
||||
int ret;
|
||||
uint16_t *refcount_block;
|
||||
void *refcount_block;
|
||||
|
||||
refcount_table_index = cluster_index >> s->refcount_block_bits;
|
||||
if (refcount_table_index >= s->refcount_table_size) {
|
||||
@ -119,16 +140,15 @@ int qcow2_get_refcount(BlockDriverState *bs, int64_t cluster_index,
|
||||
}
|
||||
|
||||
ret = qcow2_cache_get(bs, s->refcount_block_cache, refcount_block_offset,
|
||||
(void**) &refcount_block);
|
||||
&refcount_block);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
block_index = cluster_index & (s->refcount_block_size - 1);
|
||||
*refcount = be16_to_cpu(refcount_block[block_index]);
|
||||
*refcount = s->get_refcount(refcount_block, block_index);
|
||||
|
||||
ret = qcow2_cache_put(bs, s->refcount_block_cache,
|
||||
(void**) &refcount_block);
|
||||
ret = qcow2_cache_put(bs, s->refcount_block_cache, &refcount_block);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
@ -172,7 +192,7 @@ static int in_same_refcount_block(BDRVQcowState *s, uint64_t offset_a,
|
||||
* Returns 0 on success or -errno in error case
|
||||
*/
|
||||
static int alloc_refcount_block(BlockDriverState *bs,
|
||||
int64_t cluster_index, uint16_t **refcount_block)
|
||||
int64_t cluster_index, void **refcount_block)
|
||||
{
|
||||
BDRVQcowState *s = bs->opaque;
|
||||
unsigned int refcount_table_index;
|
||||
@ -199,7 +219,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||
}
|
||||
|
||||
return load_refcount_block(bs, refcount_block_offset,
|
||||
(void**) refcount_block);
|
||||
refcount_block);
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,7 +269,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||
if (in_same_refcount_block(s, new_block, cluster_index << s->cluster_bits)) {
|
||||
/* Zero the new refcount block before updating it */
|
||||
ret = qcow2_cache_get_empty(bs, s->refcount_block_cache, new_block,
|
||||
(void**) refcount_block);
|
||||
refcount_block);
|
||||
if (ret < 0) {
|
||||
goto fail_block;
|
||||
}
|
||||
@ -259,7 +279,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||
/* The block describes itself, need to update the cache */
|
||||
int block_index = (new_block >> s->cluster_bits) &
|
||||
(s->refcount_block_size - 1);
|
||||
(*refcount_block)[block_index] = cpu_to_be16(1);
|
||||
s->set_refcount(*refcount_block, block_index, 1);
|
||||
} else {
|
||||
/* Described somewhere else. This can recurse at most twice before we
|
||||
* arrive at a block that describes itself. */
|
||||
@ -277,7 +297,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||
/* Initialize the new refcount block only after updating its refcount,
|
||||
* update_refcount uses the refcount cache itself */
|
||||
ret = qcow2_cache_get_empty(bs, s->refcount_block_cache, new_block,
|
||||
(void**) refcount_block);
|
||||
refcount_block);
|
||||
if (ret < 0) {
|
||||
goto fail_block;
|
||||
}
|
||||
@ -311,7 +331,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
ret = qcow2_cache_put(bs, s->refcount_block_cache, (void**) refcount_block);
|
||||
ret = qcow2_cache_put(bs, s->refcount_block_cache, refcount_block);
|
||||
if (ret < 0) {
|
||||
goto fail_block;
|
||||
}
|
||||
@ -365,7 +385,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||
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);
|
||||
uint16_t *new_blocks = g_try_malloc0(blocks_clusters * s->cluster_size);
|
||||
void *new_blocks = g_try_malloc0(blocks_clusters * s->cluster_size);
|
||||
|
||||
assert(table_size > 0 && blocks_clusters > 0);
|
||||
if (new_table == NULL || new_blocks == NULL) {
|
||||
@ -387,7 +407,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||
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++) {
|
||||
new_blocks[block++] = cpu_to_be16(1);
|
||||
s->set_refcount(new_blocks, block++, 1);
|
||||
}
|
||||
|
||||
/* Write refcount blocks to disk */
|
||||
@ -440,7 +460,7 @@ 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, (void**) refcount_block);
|
||||
ret = load_refcount_block(bs, new_block, refcount_block);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
@ -455,7 +475,7 @@ fail_table:
|
||||
g_free(new_table);
|
||||
fail_block:
|
||||
if (*refcount_block != NULL) {
|
||||
qcow2_cache_put(bs, s->refcount_block_cache, (void**) refcount_block);
|
||||
qcow2_cache_put(bs, s->refcount_block_cache, refcount_block);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@ -541,7 +561,7 @@ static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
|
||||
{
|
||||
BDRVQcowState *s = bs->opaque;
|
||||
int64_t start, last, cluster_offset;
|
||||
uint16_t *refcount_block = NULL;
|
||||
void *refcount_block = NULL;
|
||||
int64_t old_table_index = -1;
|
||||
int ret;
|
||||
|
||||
@ -575,7 +595,7 @@ static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
|
||||
if (table_index != old_table_index) {
|
||||
if (refcount_block) {
|
||||
ret = qcow2_cache_put(bs, s->refcount_block_cache,
|
||||
(void**) &refcount_block);
|
||||
&refcount_block);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
@ -593,7 +613,7 @@ static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
|
||||
/* we can update the count and save it */
|
||||
block_index = cluster_index & (s->refcount_block_size - 1);
|
||||
|
||||
refcount = be16_to_cpu(refcount_block[block_index]);
|
||||
refcount = s->get_refcount(refcount_block, block_index);
|
||||
if (decrease ? (refcount - addend > refcount)
|
||||
: (refcount + addend < refcount ||
|
||||
refcount + addend > s->refcount_max))
|
||||
@ -609,7 +629,7 @@ static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
|
||||
if (refcount == 0 && cluster_index < s->free_cluster_index) {
|
||||
s->free_cluster_index = cluster_index;
|
||||
}
|
||||
refcount_block[block_index] = cpu_to_be16(refcount);
|
||||
s->set_refcount(refcount_block, block_index, refcount);
|
||||
|
||||
if (refcount == 0 && s->discard_passthrough[type]) {
|
||||
update_refcount_discard(bs, cluster_offset, s->cluster_size);
|
||||
@ -625,8 +645,7 @@ fail:
|
||||
/* Write last changed block to disk */
|
||||
if (refcount_block) {
|
||||
int wret;
|
||||
wret = qcow2_cache_put(bs, s->refcount_block_cache,
|
||||
(void**) &refcount_block);
|
||||
wret = qcow2_cache_put(bs, s->refcount_block_cache, &refcount_block);
|
||||
if (wret < 0) {
|
||||
return ret < 0 ? ret : wret;
|
||||
}
|
||||
@ -1118,11 +1137,11 @@ static size_t refcount_array_byte_size(BDRVQcowState *s, uint64_t entries)
|
||||
* refcount array buffer will be aligned to a cluster boundary, and the newly
|
||||
* allocated area will be zeroed.
|
||||
*/
|
||||
static int realloc_refcount_array(BDRVQcowState *s, uint16_t **array,
|
||||
static int realloc_refcount_array(BDRVQcowState *s, void **array,
|
||||
int64_t *size, int64_t new_size)
|
||||
{
|
||||
size_t old_byte_size, new_byte_size;
|
||||
uint16_t *new_ptr;
|
||||
void *new_ptr;
|
||||
|
||||
/* Round to clusters so the array can be directly written to disk */
|
||||
old_byte_size = size_to_clusters(s, refcount_array_byte_size(s, *size))
|
||||
@ -1162,12 +1181,12 @@ static int realloc_refcount_array(BDRVQcowState *s, uint16_t **array,
|
||||
*/
|
||||
static int inc_refcounts(BlockDriverState *bs,
|
||||
BdrvCheckResult *res,
|
||||
uint16_t **refcount_table,
|
||||
void **refcount_table,
|
||||
int64_t *refcount_table_size,
|
||||
int64_t offset, int64_t size)
|
||||
{
|
||||
BDRVQcowState *s = bs->opaque;
|
||||
uint64_t start, last, cluster_offset, k;
|
||||
uint64_t start, last, cluster_offset, k, refcount;
|
||||
int ret;
|
||||
|
||||
if (size <= 0) {
|
||||
@ -1188,11 +1207,14 @@ static int inc_refcounts(BlockDriverState *bs,
|
||||
}
|
||||
}
|
||||
|
||||
if (++(*refcount_table)[k] == 0) {
|
||||
refcount = s->get_refcount(*refcount_table, k);
|
||||
if (refcount == s->refcount_max) {
|
||||
fprintf(stderr, "ERROR: overflow cluster offset=0x%" PRIx64
|
||||
"\n", cluster_offset);
|
||||
res->corruptions++;
|
||||
continue;
|
||||
}
|
||||
s->set_refcount(*refcount_table, k, refcount + 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -1212,8 +1234,9 @@ enum {
|
||||
* error occurred.
|
||||
*/
|
||||
static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
uint16_t **refcount_table, int64_t *refcount_table_size, int64_t l2_offset,
|
||||
int flags)
|
||||
void **refcount_table,
|
||||
int64_t *refcount_table_size, int64_t l2_offset,
|
||||
int flags)
|
||||
{
|
||||
BDRVQcowState *s = bs->opaque;
|
||||
uint64_t *l2_table, l2_entry;
|
||||
@ -1330,7 +1353,7 @@ fail:
|
||||
*/
|
||||
static int check_refcounts_l1(BlockDriverState *bs,
|
||||
BdrvCheckResult *res,
|
||||
uint16_t **refcount_table,
|
||||
void **refcount_table,
|
||||
int64_t *refcount_table_size,
|
||||
int64_t l1_table_offset, int l1_size,
|
||||
int flags)
|
||||
@ -1529,7 +1552,7 @@ fail:
|
||||
*/
|
||||
static int check_refblocks(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
BdrvCheckMode fix, bool *rebuild,
|
||||
uint16_t **refcount_table, int64_t *nb_clusters)
|
||||
void **refcount_table, int64_t *nb_clusters)
|
||||
{
|
||||
BDRVQcowState *s = bs->opaque;
|
||||
int64_t i, size;
|
||||
@ -1614,9 +1637,10 @@ resize_fail:
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
if ((*refcount_table)[cluster] != 1) {
|
||||
if (s->get_refcount(*refcount_table, cluster) != 1) {
|
||||
fprintf(stderr, "ERROR refcount block %" PRId64
|
||||
" refcount=%d\n", i, (*refcount_table)[cluster]);
|
||||
" refcount=%" PRIu64 "\n", i,
|
||||
s->get_refcount(*refcount_table, cluster));
|
||||
res->corruptions++;
|
||||
*rebuild = true;
|
||||
}
|
||||
@ -1631,7 +1655,7 @@ resize_fail:
|
||||
*/
|
||||
static int calculate_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
BdrvCheckMode fix, bool *rebuild,
|
||||
uint16_t **refcount_table, int64_t *nb_clusters)
|
||||
void **refcount_table, int64_t *nb_clusters)
|
||||
{
|
||||
BDRVQcowState *s = bs->opaque;
|
||||
int64_t i;
|
||||
@ -1695,7 +1719,7 @@ static int calculate_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
static void compare_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
BdrvCheckMode fix, bool *rebuild,
|
||||
int64_t *highest_cluster,
|
||||
uint16_t *refcount_table, int64_t nb_clusters)
|
||||
void *refcount_table, int64_t nb_clusters)
|
||||
{
|
||||
BDRVQcowState *s = bs->opaque;
|
||||
int64_t i;
|
||||
@ -1711,7 +1735,7 @@ static void compare_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
continue;
|
||||
}
|
||||
|
||||
refcount2 = refcount_table[i];
|
||||
refcount2 = s->get_refcount(refcount_table, i);
|
||||
|
||||
if (refcount1 > 0 || refcount2 > 0) {
|
||||
*highest_cluster = i;
|
||||
@ -1770,7 +1794,7 @@ static void compare_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
*/
|
||||
static int64_t alloc_clusters_imrt(BlockDriverState *bs,
|
||||
int cluster_count,
|
||||
uint16_t **refcount_table,
|
||||
void **refcount_table,
|
||||
int64_t *imrt_nb_clusters,
|
||||
int64_t *first_free_cluster)
|
||||
{
|
||||
@ -1787,7 +1811,7 @@ static int64_t alloc_clusters_imrt(BlockDriverState *bs,
|
||||
contiguous_free_clusters < cluster_count;
|
||||
cluster++)
|
||||
{
|
||||
if (!(*refcount_table)[cluster]) {
|
||||
if (!s->get_refcount(*refcount_table, cluster)) {
|
||||
contiguous_free_clusters++;
|
||||
if (first_gap) {
|
||||
/* If this is the first free cluster found, update
|
||||
@ -1825,7 +1849,7 @@ static int64_t alloc_clusters_imrt(BlockDriverState *bs,
|
||||
/* Go back to the first free cluster */
|
||||
cluster -= contiguous_free_clusters;
|
||||
for (i = 0; i < cluster_count; i++) {
|
||||
(*refcount_table)[cluster + i] = 1;
|
||||
s->set_refcount(*refcount_table, cluster + i, 1);
|
||||
}
|
||||
|
||||
return cluster << s->cluster_bits;
|
||||
@ -1841,7 +1865,7 @@ static int64_t alloc_clusters_imrt(BlockDriverState *bs,
|
||||
*/
|
||||
static int rebuild_refcount_structure(BlockDriverState *bs,
|
||||
BdrvCheckResult *res,
|
||||
uint16_t **refcount_table,
|
||||
void **refcount_table,
|
||||
int64_t *nb_clusters)
|
||||
{
|
||||
BDRVQcowState *s = bs->opaque;
|
||||
@ -1849,8 +1873,8 @@ static int rebuild_refcount_structure(BlockDriverState *bs,
|
||||
int64_t refblock_offset, refblock_start, refblock_index;
|
||||
uint32_t reftable_size = 0;
|
||||
uint64_t *on_disk_reftable = NULL;
|
||||
uint16_t *on_disk_refblock;
|
||||
int i, ret = 0;
|
||||
void *on_disk_refblock;
|
||||
int ret = 0;
|
||||
struct {
|
||||
uint64_t reftable_offset;
|
||||
uint32_t reftable_clusters;
|
||||
@ -1860,7 +1884,7 @@ static int rebuild_refcount_structure(BlockDriverState *bs,
|
||||
|
||||
write_refblocks:
|
||||
for (; cluster < *nb_clusters; cluster++) {
|
||||
if (!(*refcount_table)[cluster]) {
|
||||
if (!s->get_refcount(*refcount_table, cluster)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1933,17 +1957,13 @@ write_refblocks:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
on_disk_refblock = qemu_blockalign0(bs->file, s->cluster_size);
|
||||
for (i = 0; i < s->refcount_block_size &&
|
||||
refblock_start + i < *nb_clusters; i++)
|
||||
{
|
||||
on_disk_refblock[i] =
|
||||
cpu_to_be16((*refcount_table)[refblock_start + i]);
|
||||
}
|
||||
/* The size of *refcount_table is always cluster-aligned, therefore the
|
||||
* write operation will not overflow */
|
||||
on_disk_refblock = (void *)((char *) *refcount_table +
|
||||
refblock_index * s->cluster_size);
|
||||
|
||||
ret = bdrv_write(bs->file, refblock_offset / BDRV_SECTOR_SIZE,
|
||||
(void *)on_disk_refblock, s->cluster_sectors);
|
||||
qemu_vfree(on_disk_refblock);
|
||||
on_disk_refblock, s->cluster_sectors);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "ERROR writing refblock: %s\n", strerror(-ret));
|
||||
goto fail;
|
||||
@ -2038,7 +2058,7 @@ int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
BDRVQcowState *s = bs->opaque;
|
||||
BdrvCheckResult pre_compare_res;
|
||||
int64_t size, highest_cluster, nb_clusters;
|
||||
uint16_t *refcount_table = NULL;
|
||||
void *refcount_table = NULL;
|
||||
bool rebuild = false;
|
||||
int ret;
|
||||
|
||||
@ -2087,7 +2107,7 @@ int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
/* Because the old reftable has been exchanged for a new one the
|
||||
* references have to be recalculated */
|
||||
rebuild = false;
|
||||
memset(refcount_table, 0, nb_clusters * sizeof(uint16_t));
|
||||
memset(refcount_table, 0, refcount_array_byte_size(s, nb_clusters));
|
||||
ret = calculate_refcounts(bs, res, 0, &rebuild, &refcount_table,
|
||||
&nb_clusters);
|
||||
if (ret < 0) {
|
||||
|
@ -213,6 +213,11 @@ typedef struct Qcow2DiscardRegion {
|
||||
QTAILQ_ENTRY(Qcow2DiscardRegion) next;
|
||||
} Qcow2DiscardRegion;
|
||||
|
||||
typedef uint64_t Qcow2GetRefcountFunc(const void *refcount_array,
|
||||
uint64_t index);
|
||||
typedef void Qcow2SetRefcountFunc(void *refcount_array,
|
||||
uint64_t index, uint64_t value);
|
||||
|
||||
typedef struct BDRVQcowState {
|
||||
int cluster_bits;
|
||||
int cluster_size;
|
||||
@ -261,6 +266,9 @@ typedef struct BDRVQcowState {
|
||||
int refcount_bits;
|
||||
uint64_t refcount_max;
|
||||
|
||||
Qcow2GetRefcountFunc *get_refcount;
|
||||
Qcow2SetRefcountFunc *set_refcount;
|
||||
|
||||
bool discard_passthrough[QCOW2_DISCARD_MAX];
|
||||
|
||||
int overlap_check; /* bitmask of Qcow2MetadataOverlap values */
|
||||
|
Loading…
Reference in New Issue
Block a user