static int rebuild_refcount_structure(BlockDriverState *bs,

                                      BdrvCheckResult *res,

                                      uint16_t **refcount_table,

                                      int64_t *nb_clusters)

{

    BDRVQcowState *s = bs->opaque;

    int64_t first_free_cluster = 0, reftable_offset = -1, cluster = 0;

    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;

    struct {

        uint64_t reftable_offset;

        uint32_t reftable_clusters;

    } QEMU_PACKED reftable_offset_and_clusters;



    qcow2_cache_empty(bs, s->refcount_block_cache);



write_refblocks:

    for (; cluster < *nb_clusters; cluster++) {

        if (!(*refcount_table)[cluster]) {

            continue;

        }



        refblock_index = cluster >> s->refcount_block_bits;

        refblock_start = refblock_index << s->refcount_block_bits;



        /* Don't allocate a cluster in a refblock already written to disk */

        if (first_free_cluster < refblock_start) {

            first_free_cluster = refblock_start;

        }

        refblock_offset = alloc_clusters_imrt(bs, 1, refcount_table,

                                              nb_clusters, &first_free_cluster);

        if (refblock_offset < 0) {

            fprintf(stderr, "ERROR allocating refblock: %s\n",

                    strerror(-refblock_offset));

            res->check_errors++;

            ret = refblock_offset;

            goto fail;

        }



        if (reftable_size <= refblock_index) {

            uint32_t old_reftable_size = reftable_size;

            uint64_t *new_on_disk_reftable;



            reftable_size = ROUND_UP((refblock_index + 1) * sizeof(uint64_t),

                                     s->cluster_size) / sizeof(uint64_t);

            new_on_disk_reftable = g_try_realloc(on_disk_reftable,

                                                 reftable_size *

                                                 sizeof(uint64_t));

            if (!new_on_disk_reftable) {

                res->check_errors++;

                ret = -ENOMEM;

                goto fail;

            }

            on_disk_reftable = new_on_disk_reftable;



            memset(on_disk_reftable + old_reftable_size, 0,

                   (reftable_size - old_reftable_size) * sizeof(uint64_t));



            /* The offset we have for the reftable is now no longer valid;

             * this will leak that range, but we can easily fix that by running

             * a leak-fixing check after this rebuild operation */

            reftable_offset = -1;

        }

        on_disk_reftable[refblock_index] = refblock_offset;



        /* If this is apparently the last refblock (for now), try to squeeze the

         * reftable in */

        if (refblock_index == (*nb_clusters - 1) >> s->refcount_block_bits &&

            reftable_offset < 0)

        {

            uint64_t reftable_clusters = size_to_clusters(s, reftable_size *

                                                          sizeof(uint64_t));

            reftable_offset = alloc_clusters_imrt(bs, reftable_clusters,

                                                  refcount_table, nb_clusters,

                                                  &first_free_cluster);

            if (reftable_offset < 0) {

                fprintf(stderr, "ERROR allocating reftable: %s\n",

                        strerror(-reftable_offset));

                res->check_errors++;

                ret = reftable_offset;

                goto fail;

            }

        }



        ret = qcow2_pre_write_overlap_check(bs, 0, refblock_offset,

                                            s->cluster_size);

        if (ret < 0) {

            fprintf(stderr, "ERROR writing refblock: %s\n", strerror(-ret));

            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]);

        }



        ret = bdrv_write(bs->file, refblock_offset / BDRV_SECTOR_SIZE,

                         (void *)on_disk_refblock, s->cluster_sectors);

        qemu_vfree(on_disk_refblock);

        if (ret < 0) {

            fprintf(stderr, "ERROR writing refblock: %s\n", strerror(-ret));

            goto fail;

        }



        /* Go to the end of this refblock */

        cluster = refblock_start + s->refcount_block_size - 1;

    }



    if (reftable_offset < 0) {

        uint64_t post_refblock_start, reftable_clusters;



        post_refblock_start = ROUND_UP(*nb_clusters, s->refcount_block_size);

        reftable_clusters = size_to_clusters(s,

                                             reftable_size * sizeof(uint64_t));

        /* Not pretty but simple */

        if (first_free_cluster < post_refblock_start) {

            first_free_cluster = post_refblock_start;

        }

        reftable_offset = alloc_clusters_imrt(bs, reftable_clusters,

                                              refcount_table, nb_clusters,

                                              &first_free_cluster);

        if (reftable_offset < 0) {

            fprintf(stderr, "ERROR allocating reftable: %s\n",

                    strerror(-reftable_offset));

            res->check_errors++;

            ret = reftable_offset;

            goto fail;

        }



        goto write_refblocks;

    }



    assert(on_disk_reftable);



    for (refblock_index = 0; refblock_index < reftable_size; refblock_index++) {

        cpu_to_be64s(&on_disk_reftable[refblock_index]);

    }



    ret = qcow2_pre_write_overlap_check(bs, 0, reftable_offset,

                                        reftable_size * sizeof(uint64_t));

    if (ret < 0) {

        fprintf(stderr, "ERROR writing reftable: %s\n", strerror(-ret));

        goto fail;

    }



    assert(reftable_size < INT_MAX / sizeof(uint64_t));

    ret = bdrv_pwrite(bs->file, reftable_offset, on_disk_reftable,

                      reftable_size * sizeof(uint64_t));

    if (ret < 0) {

        fprintf(stderr, "ERROR writing reftable: %s\n", strerror(-ret));

        goto fail;

    }



    /* Enter new reftable into the image header */

    cpu_to_be64w(&reftable_offset_and_clusters.reftable_offset,

                 reftable_offset);

    cpu_to_be32w(&reftable_offset_and_clusters.reftable_clusters,

                 size_to_clusters(s, reftable_size * sizeof(uint64_t)));

    ret = bdrv_pwrite_sync(bs->file, offsetof(QCowHeader,

                                              refcount_table_offset),

                           &reftable_offset_and_clusters,

                           sizeof(reftable_offset_and_clusters));

    if (ret < 0) {

        fprintf(stderr, "ERROR setting reftable: %s\n", strerror(-ret));

        goto fail;

    }



    for (refblock_index = 0; refblock_index < reftable_size; refblock_index++) {

        be64_to_cpus(&on_disk_reftable[refblock_index]);

    }

    s->refcount_table = on_disk_reftable;

    s->refcount_table_offset = reftable_offset;

    s->refcount_table_size = reftable_size;



    return 0;



fail:

    g_free(on_disk_reftable);

    return ret;

}
