Commit afa50193 authored by Max Reitz's avatar Max Reitz Committed by Kevin Wolf
Browse files

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: default avatarMax Reitz <mreitz@redhat.com>
Signed-off-by: default avatarKevin Wolf <kwolf@redhat.com>
parent e23e400e
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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",
+146 −2
Original line number Diff line number Diff line
@@ -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]);

                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++;
                }
            }
        }
    }
+1 −0
Original line number Diff line number Diff line
@@ -413,6 +413,7 @@ typedef enum {

    BLKDBG_REFTABLE_LOAD,
    BLKDBG_REFTABLE_GROW,
    BLKDBG_REFTABLE_UPDATE,

    BLKDBG_REFBLOCK_LOAD,
    BLKDBG_REFBLOCK_UPDATE,