Commit b156d51b authored by Kevin Wolf's avatar Kevin Wolf
Browse files

Merge remote-tracking branch 'mreitz/tags/pull-block-2017-09-26' into queue-block



Block patches

# gpg: Signature made Tue Sep 26 15:01:00 2017 CEST
# gpg:                using RSA key F407DB0061D5CF40
# gpg: Good signature from "Max Reitz <mreitz@redhat.com>"
# Primary key fingerprint: 91BE B60A 30DB 3E88 57D1  1829 F407 DB00 61D5 CF40

* mreitz/tags/pull-block-2017-09-26:
  block/qcow2-bitmap: fix use of uninitialized pointer
  qemu-iotests: add shrinking image test
  qcow2: add shrink image support
  qcow2: add qcow2_cache_discard
  qemu-img: add --shrink flag for resize

Signed-off-by: default avatarKevin Wolf <kwolf@redhat.com>
parents 69ff158b 5330f32b
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -602,7 +602,7 @@ static Qcow2BitmapList *bitmap_list_load(BlockDriverState *bs, uint64_t offset,
            goto fail;
        }

        bm = g_new(Qcow2Bitmap, 1);
        bm = g_new0(Qcow2Bitmap, 1);
        bm->table.offset = e->bitmap_table_offset;
        bm->table.size = e->bitmap_table_size;
        bm->flags = e->flags;
+26 −0
Original line number Diff line number Diff line
@@ -411,3 +411,29 @@ void qcow2_cache_entry_mark_dirty(BlockDriverState *bs, Qcow2Cache *c,
    assert(c->entries[i].offset != 0);
    c->entries[i].dirty = true;
}

void *qcow2_cache_is_table_offset(BlockDriverState *bs, Qcow2Cache *c,
                                  uint64_t offset)
{
    int i;

    for (i = 0; i < c->size; i++) {
        if (c->entries[i].offset == offset) {
            return qcow2_cache_get_table_addr(bs, c, i);
        }
    }
    return NULL;
}

void qcow2_cache_discard(BlockDriverState *bs, Qcow2Cache *c, void *table)
{
    int i = qcow2_cache_get_table_idx(bs, c, table);

    assert(c->entries[i].ref == 0);

    c->entries[i].offset = 0;
    c->entries[i].lru_counter = 0;
    c->entries[i].dirty = false;

    qcow2_cache_table_release(bs, c, i, 1);
}
+50 −0
Original line number Diff line number Diff line
@@ -32,6 +32,56 @@
#include "qemu/bswap.h"
#include "trace.h"

int qcow2_shrink_l1_table(BlockDriverState *bs, uint64_t exact_size)
{
    BDRVQcow2State *s = bs->opaque;
    int new_l1_size, i, ret;

    if (exact_size >= s->l1_size) {
        return 0;
    }

    new_l1_size = exact_size;

#ifdef DEBUG_ALLOC2
    fprintf(stderr, "shrink l1_table from %d to %d\n", s->l1_size, new_l1_size);
#endif

    BLKDBG_EVENT(bs->file, BLKDBG_L1_SHRINK_WRITE_TABLE);
    ret = bdrv_pwrite_zeroes(bs->file, s->l1_table_offset +
                                       new_l1_size * sizeof(uint64_t),
                             (s->l1_size - new_l1_size) * sizeof(uint64_t), 0);
    if (ret < 0) {
        goto fail;
    }

    ret = bdrv_flush(bs->file->bs);
    if (ret < 0) {
        goto fail;
    }

    BLKDBG_EVENT(bs->file, BLKDBG_L1_SHRINK_FREE_L2_CLUSTERS);
    for (i = s->l1_size - 1; i > new_l1_size - 1; i--) {
        if ((s->l1_table[i] & L1E_OFFSET_MASK) == 0) {
            continue;
        }
        qcow2_free_clusters(bs, s->l1_table[i] & L1E_OFFSET_MASK,
                            s->cluster_size, QCOW2_DISCARD_ALWAYS);
        s->l1_table[i] = 0;
    }
    return 0;

fail:
    /*
     * If the write in the l1_table failed the image may contain a partially
     * overwritten l1_table. In this case it would be better to clear the
     * l1_table in memory to avoid possible image corruption.
     */
    memset(s->l1_table + new_l1_size, 0,
           (s->l1_size - new_l1_size) * sizeof(uint64_t));
    return ret;
}

int qcow2_grow_l1_table(BlockDriverState *bs, uint64_t min_size,
                        bool exact_size)
{
+138 −2
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@
#include "block/qcow2.h"
#include "qemu/range.h"
#include "qemu/bswap.h"
#include "qemu/cutils.h"

static int64_t alloc_clusters_noref(BlockDriverState *bs, uint64_t size);
static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
@@ -861,10 +862,26 @@ static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
        }
        s->set_refcount(refcount_block, block_index, refcount);

        if (refcount == 0 && s->discard_passthrough[type]) {
        if (refcount == 0) {
            void *table;

            table = qcow2_cache_is_table_offset(bs, s->refcount_block_cache,
                                                offset);
            if (table != NULL) {
                qcow2_cache_put(bs, s->refcount_block_cache, &refcount_block);
                qcow2_cache_discard(bs, s->refcount_block_cache, table);
            }

            table = qcow2_cache_is_table_offset(bs, s->l2_table_cache, offset);
            if (table != NULL) {
                qcow2_cache_discard(bs, s->l2_table_cache, table);
            }

            if (s->discard_passthrough[type]) {
                update_refcount_discard(bs, cluster_offset, s->cluster_size);
            }
        }
    }

    ret = 0;
fail:
@@ -3045,3 +3062,122 @@ done:
    qemu_vfree(new_refblock);
    return ret;
}

static int qcow2_discard_refcount_block(BlockDriverState *bs,
                                        uint64_t discard_block_offs)
{
    BDRVQcow2State *s = bs->opaque;
    uint64_t refblock_offs = get_refblock_offset(s, discard_block_offs);
    uint64_t cluster_index = discard_block_offs >> s->cluster_bits;
    uint32_t block_index = cluster_index & (s->refcount_block_size - 1);
    void *refblock;
    int ret;

    assert(discard_block_offs != 0);

    ret = qcow2_cache_get(bs, s->refcount_block_cache, refblock_offs,
                          &refblock);
    if (ret < 0) {
        return ret;
    }

    if (s->get_refcount(refblock, block_index) != 1) {
        qcow2_signal_corruption(bs, true, -1, -1, "Invalid refcount:"
                                " refblock offset %#" PRIx64
                                ", reftable index %u"
                                ", block offset %#" PRIx64
                                ", refcount %#" PRIx64,
                                refblock_offs,
                                offset_to_reftable_index(s, discard_block_offs),
                                discard_block_offs,
                                s->get_refcount(refblock, block_index));
        qcow2_cache_put(bs, s->refcount_block_cache, &refblock);
        return -EINVAL;
    }
    s->set_refcount(refblock, block_index, 0);

    qcow2_cache_entry_mark_dirty(bs, s->refcount_block_cache, refblock);

    qcow2_cache_put(bs, s->refcount_block_cache, &refblock);

    if (cluster_index < s->free_cluster_index) {
        s->free_cluster_index = cluster_index;
    }

    refblock = qcow2_cache_is_table_offset(bs, s->refcount_block_cache,
                                           discard_block_offs);
    if (refblock) {
        /* discard refblock from the cache if refblock is cached */
        qcow2_cache_discard(bs, s->refcount_block_cache, refblock);
    }
    update_refcount_discard(bs, discard_block_offs, s->cluster_size);

    return 0;
}

int qcow2_shrink_reftable(BlockDriverState *bs)
{
    BDRVQcow2State *s = bs->opaque;
    uint64_t *reftable_tmp =
        g_malloc(s->refcount_table_size * sizeof(uint64_t));
    int i, ret;

    for (i = 0; i < s->refcount_table_size; i++) {
        int64_t refblock_offs = s->refcount_table[i] & REFT_OFFSET_MASK;
        void *refblock;
        bool unused_block;

        if (refblock_offs == 0) {
            reftable_tmp[i] = 0;
            continue;
        }
        ret = qcow2_cache_get(bs, s->refcount_block_cache, refblock_offs,
                              &refblock);
        if (ret < 0) {
            goto out;
        }

        /* the refblock has own reference */
        if (i == offset_to_reftable_index(s, refblock_offs)) {
            uint64_t block_index = (refblock_offs >> s->cluster_bits) &
                                   (s->refcount_block_size - 1);
            uint64_t refcount = s->get_refcount(refblock, block_index);

            s->set_refcount(refblock, block_index, 0);

            unused_block = buffer_is_zero(refblock, s->cluster_size);

            s->set_refcount(refblock, block_index, refcount);
        } else {
            unused_block = buffer_is_zero(refblock, s->cluster_size);
        }
        qcow2_cache_put(bs, s->refcount_block_cache, &refblock);

        reftable_tmp[i] = unused_block ? 0 : cpu_to_be64(s->refcount_table[i]);
    }

    ret = bdrv_pwrite_sync(bs->file, s->refcount_table_offset, reftable_tmp,
                           s->refcount_table_size * sizeof(uint64_t));
    /*
     * If the write in the reftable failed the image may contain a partially
     * overwritten reftable. In this case it would be better to clear the
     * reftable in memory to avoid possible image corruption.
     */
    for (i = 0; i < s->refcount_table_size; i++) {
        if (s->refcount_table[i] && !reftable_tmp[i]) {
            if (ret == 0) {
                ret = qcow2_discard_refcount_block(bs, s->refcount_table[i] &
                                                       REFT_OFFSET_MASK);
            }
            s->refcount_table[i] = 0;
        }
    }

    if (!s->cache_discards) {
        qcow2_process_discards(bs, ret);
    }

out:
    g_free(reftable_tmp);
    return ret;
}
+34 −9
Original line number Diff line number Diff line
@@ -3104,19 +3104,44 @@ static int qcow2_truncate(BlockDriverState *bs, int64_t offset,
    }

    old_length = bs->total_sectors * 512;
    new_l1_size = size_to_l1(s, offset);

    /* shrinking is currently not supported */
    if (offset < old_length) {
        error_setg(errp, "qcow2 doesn't support shrinking images yet");
        return -ENOTSUP;
        if (prealloc != PREALLOC_MODE_OFF) {
            error_setg(errp,
                       "Preallocation can't be used for shrinking an image");
            return -EINVAL;
        }

    new_l1_size = size_to_l1(s, offset);
        ret = qcow2_cluster_discard(bs, ROUND_UP(offset, s->cluster_size),
                                    old_length - ROUND_UP(offset,
                                                          s->cluster_size),
                                    QCOW2_DISCARD_ALWAYS, true);
        if (ret < 0) {
            error_setg_errno(errp, -ret, "Failed to discard cropped clusters");
            return ret;
        }

        ret = qcow2_shrink_l1_table(bs, new_l1_size);
        if (ret < 0) {
            error_setg_errno(errp, -ret,
                             "Failed to reduce the number of L2 tables");
            return ret;
        }

        ret = qcow2_shrink_reftable(bs);
        if (ret < 0) {
            error_setg_errno(errp, -ret,
                             "Failed to discard unused refblocks");
            return ret;
        }
    } else {
        ret = qcow2_grow_l1_table(bs, new_l1_size, true);
        if (ret < 0) {
            error_setg_errno(errp, -ret, "Failed to grow the L1 table");
            return ret;
        }
    }

    switch (prealloc) {
    case PREALLOC_MODE_OFF:
Loading