qcow2-refcount: Repair shared refcount blocks

If the refcount of a refcount block is greater than one, we can at least
try to repair that problem by duplicating the affected block.

Signed-off-by: Max Reitz <mreitz@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
This commit is contained in:
Max Reitz 2013-09-02 09:25:10 +02:00 committed by Kevin Wolf
parent e23e400ec6
commit afa50193cd
3 changed files with 148 additions and 2 deletions

View file

@ -168,6 +168,7 @@ static const char *event_names[BLKDBG_EVENT_MAX] = {
[BLKDBG_REFTABLE_LOAD] = "reftable_load",
[BLKDBG_REFTABLE_GROW] = "reftable_grow",
[BLKDBG_REFTABLE_UPDATE] = "reftable_update",
[BLKDBG_REFBLOCK_LOAD] = "refblock_load",
[BLKDBG_REFBLOCK_UPDATE] = "refblock_update",

View file

@ -1337,6 +1337,121 @@ fail:
return ret;
}
/*
* Writes one sector of the refcount table to the disk
*/
#define RT_ENTRIES_PER_SECTOR (512 / sizeof(uint64_t))
static int write_reftable_entry(BlockDriverState *bs, int rt_index)
{
BDRVQcowState *s = bs->opaque;
uint64_t buf[RT_ENTRIES_PER_SECTOR];
int rt_start_index;
int i, ret;
rt_start_index = rt_index & ~(RT_ENTRIES_PER_SECTOR - 1);
for (i = 0; i < RT_ENTRIES_PER_SECTOR; i++) {
buf[i] = cpu_to_be64(s->refcount_table[rt_start_index + i]);
}
ret = qcow2_pre_write_overlap_check(bs,
QCOW2_OL_DEFAULT & ~QCOW2_OL_REFCOUNT_TABLE,
s->refcount_table_offset + rt_start_index * sizeof(uint64_t),
sizeof(buf));
if (ret < 0) {
return ret;
}
BLKDBG_EVENT(bs->file, BLKDBG_REFTABLE_UPDATE);
ret = bdrv_pwrite_sync(bs->file, s->refcount_table_offset +
rt_start_index * sizeof(uint64_t), buf, sizeof(buf));
if (ret < 0) {
return ret;
}
return 0;
}
/*
* Allocates a new cluster for the given refcount block (represented by its
* offset in the image file) and copies the current content there. This function
* does _not_ decrement the reference count for the currently occupied cluster.
*
* This function prints an informative message to stderr on error (and returns
* -errno); on success, 0 is returned.
*/
static int64_t realloc_refcount_block(BlockDriverState *bs, int reftable_index,
uint64_t offset)
{
BDRVQcowState *s = bs->opaque;
int64_t new_offset = 0;
void *refcount_block = NULL;
int ret;
/* allocate new refcount block */
new_offset = qcow2_alloc_clusters(bs, s->cluster_size);
if (new_offset < 0) {
fprintf(stderr, "Could not allocate new cluster: %s\n",
strerror(-new_offset));
ret = new_offset;
goto fail;
}
/* fetch current refcount block content */
ret = qcow2_cache_get(bs, s->refcount_block_cache, offset, &refcount_block);
if (ret < 0) {
fprintf(stderr, "Could not fetch refcount block: %s\n", strerror(-ret));
goto fail;
}
/* new block has not yet been entered into refcount table, therefore it is
* no refcount block yet (regarding this check) */
ret = qcow2_pre_write_overlap_check(bs, QCOW2_OL_DEFAULT, new_offset,
s->cluster_size);
if (ret < 0) {
fprintf(stderr, "Could not write refcount block; metadata overlap "
"check failed: %s\n", strerror(-ret));
/* the image will be marked corrupt, so don't even attempt on freeing
* the cluster */
new_offset = 0;
goto fail;
}
/* write to new block */
ret = bdrv_write(bs->file, new_offset / BDRV_SECTOR_SIZE, refcount_block,
s->cluster_sectors);
if (ret < 0) {
fprintf(stderr, "Could not write refcount block: %s\n", strerror(-ret));
goto fail;
}
/* update refcount table */
assert(!(new_offset & (s->cluster_size - 1)));
s->refcount_table[reftable_index] = new_offset;
ret = write_reftable_entry(bs, reftable_index);
if (ret < 0) {
fprintf(stderr, "Could not update refcount table: %s\n",
strerror(-ret));
goto fail;
}
fail:
if (new_offset && (ret < 0)) {
qcow2_free_clusters(bs, new_offset, s->cluster_size,
QCOW2_DISCARD_ALWAYS);
}
if (refcount_block) {
if (ret < 0) {
qcow2_cache_put(bs, s->refcount_block_cache, &refcount_block);
} else {
ret = qcow2_cache_put(bs, s->refcount_block_cache, &refcount_block);
}
}
if (ret < 0) {
return ret;
}
return new_offset;
}
/*
* Checks an image for refcount consistency.
*
@ -1413,10 +1528,39 @@ int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
inc_refcounts(bs, res, refcount_table, nb_clusters,
offset, s->cluster_size);
if (refcount_table[cluster] != 1) {
fprintf(stderr, "ERROR refcount block %" PRId64
fprintf(stderr, "%s refcount block %" PRId64
" refcount=%d\n",
fix & BDRV_FIX_ERRORS ? "Repairing" :
"ERROR",
i, refcount_table[cluster]);
res->corruptions++;
if (fix & BDRV_FIX_ERRORS) {
int64_t new_offset;
new_offset = realloc_refcount_block(bs, i, offset);
if (new_offset < 0) {
res->corruptions++;
continue;
}
/* update refcounts */
if ((new_offset >> s->cluster_bits) >= nb_clusters) {
/* increase refcount_table size if necessary */
int old_nb_clusters = nb_clusters;
nb_clusters = (new_offset >> s->cluster_bits) + 1;
refcount_table = g_realloc(refcount_table,
nb_clusters * sizeof(uint16_t));
memset(&refcount_table[old_nb_clusters], 0, (nb_clusters
- old_nb_clusters) * sizeof(uint16_t));
}
refcount_table[cluster]--;
inc_refcounts(bs, res, refcount_table, nb_clusters,
new_offset, s->cluster_size);
res->corruptions_fixed++;
} else {
res->corruptions++;
}
}
}
}

View file

@ -413,6 +413,7 @@ typedef enum {
BLKDBG_REFTABLE_LOAD,
BLKDBG_REFTABLE_GROW,
BLKDBG_REFTABLE_UPDATE,
BLKDBG_REFBLOCK_LOAD,
BLKDBG_REFBLOCK_UPDATE,