mirror of https://github.com/xqemu/xqemu.git
Block patches for the block queue
-----BEGIN PGP SIGNATURE----- iQEcBAABAgAGBQJagwuNAAoJEPQH2wBh1c9ASf4H/R7am/neac7JIihU70N+bF96 enuALHMXBQWRWVIDhHzGtxcxgTt9aC5RG4b5HxnX5DD+tFJTInW6WhMQd76lQ81N QQ+rH/jcaw6ef/RD70DURQ54PWvcLUrXDObilH+2WBosxqs5VfgW3P/8qTgGEWn5 /vyIAi0ISct5kKH8i5TpWMjSj/iSXc6blbYz74F8JpyxbW114H9OXb+VJgdTvjV7 HH0+yEmUYC5+xifhsLVpX3GmXLQ4zOabEgAW4r8Un9F7wRQNcsfyGeSK5tdc547L IKVPzJL8CjuE7ZM3+suVDepMB2WviLFXfQza3MqDnHr0JpC9AHLNqa46G4Bvdvk= =tGRE -----END PGP SIGNATURE----- Merge remote-tracking branch 'mreitz/tags/pull-block-2018-02-13' into queue-block Block patches for the block queue # gpg: Signature made Tue Feb 13 17:00:13 2018 CET # 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-2018-02-13: (40 commits) iotests: Add l2-cache-entry-size to iotest 137 iotests: Test downgrading an image using a small L2 slice size iotests: Test valid values of l2-cache-entry-size qcow2: Allow configuring the L2 slice size qcow2: Rename l2_table in count_cow_clusters() qcow2: Rename l2_table in count_contiguous_clusters_unallocated() qcow2: Rename l2_table in count_contiguous_clusters() qcow2: Rename l2_table in qcow2_alloc_compressed_cluster_offset() qcow2: Update qcow2_truncate() to support L2 slices qcow2: Update expand_zero_clusters_in_l1() to support L2 slices qcow2: Prepare expand_zero_clusters_in_l1() for adding L2 slice support qcow2: Read refcount before L2 table in expand_zero_clusters_in_l1() qcow2: Update qcow2_update_snapshot_refcount() to support L2 slices qcow2: Prepare qcow2_update_snapshot_refcount() for adding L2 slice support qcow2: Update zero_single_l2() to support L2 slices qcow2: Update discard_single_l2() to support L2 slices qcow2: Update handle_alloc() to support L2 slices qcow2: Update handle_copied() to support L2 slices qcow2: Update qcow2_alloc_cluster_link_l2() to support L2 slices qcow2: Update qcow2_get_cluster_offset() to support L2 slices ... Signed-off-by: Kevin Wolf <kwolf@redhat.com>
This commit is contained in:
commit
0a4dc980e6
|
@ -52,8 +52,6 @@ struct BdrvDirtyBitmap {
|
||||||
Such operations must fail and both the image
|
Such operations must fail and both the image
|
||||||
and this bitmap must remain unchanged while
|
and this bitmap must remain unchanged while
|
||||||
this flag is set. */
|
this flag is set. */
|
||||||
bool autoload; /* For persistent bitmaps: bitmap must be
|
|
||||||
autoloaded on image opening */
|
|
||||||
bool persistent; /* bitmap must be saved to owner disk image */
|
bool persistent; /* bitmap must be saved to owner disk image */
|
||||||
QLIST_ENTRY(BdrvDirtyBitmap) list;
|
QLIST_ENTRY(BdrvDirtyBitmap) list;
|
||||||
};
|
};
|
||||||
|
@ -104,7 +102,6 @@ void bdrv_dirty_bitmap_make_anon(BdrvDirtyBitmap *bitmap)
|
||||||
g_free(bitmap->name);
|
g_free(bitmap->name);
|
||||||
bitmap->name = NULL;
|
bitmap->name = NULL;
|
||||||
bitmap->persistent = false;
|
bitmap->persistent = false;
|
||||||
bitmap->autoload = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with BQL taken. */
|
/* Called with BQL taken. */
|
||||||
|
@ -261,8 +258,6 @@ BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
|
||||||
bitmap->successor = NULL;
|
bitmap->successor = NULL;
|
||||||
successor->persistent = bitmap->persistent;
|
successor->persistent = bitmap->persistent;
|
||||||
bitmap->persistent = false;
|
bitmap->persistent = false;
|
||||||
successor->autoload = bitmap->autoload;
|
|
||||||
bitmap->autoload = false;
|
|
||||||
bdrv_release_dirty_bitmap(bs, bitmap);
|
bdrv_release_dirty_bitmap(bs, bitmap);
|
||||||
|
|
||||||
return successor;
|
return successor;
|
||||||
|
@ -666,19 +661,6 @@ bool bdrv_has_readonly_bitmaps(BlockDriverState *bs)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with BQL taken. */
|
|
||||||
void bdrv_dirty_bitmap_set_autoload(BdrvDirtyBitmap *bitmap, bool autoload)
|
|
||||||
{
|
|
||||||
qemu_mutex_lock(bitmap->mutex);
|
|
||||||
bitmap->autoload = autoload;
|
|
||||||
qemu_mutex_unlock(bitmap->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool bdrv_dirty_bitmap_get_autoload(const BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
return bitmap->autoload;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Called with BQL taken. */
|
/* Called with BQL taken. */
|
||||||
void bdrv_dirty_bitmap_set_persistance(BdrvDirtyBitmap *bitmap, bool persistent)
|
void bdrv_dirty_bitmap_set_persistance(BdrvDirtyBitmap *bitmap, bool persistent)
|
||||||
{
|
{
|
||||||
|
|
|
@ -933,14 +933,14 @@ static void set_readonly_helper(gpointer bitmap, gpointer value)
|
||||||
bdrv_dirty_bitmap_set_readonly(bitmap, (bool)value);
|
bdrv_dirty_bitmap_set_readonly(bitmap, (bool)value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* qcow2_load_autoloading_dirty_bitmaps()
|
/* qcow2_load_dirty_bitmaps()
|
||||||
* Return value is a hint for caller: true means that the Qcow2 header was
|
* Return value is a hint for caller: true means that the Qcow2 header was
|
||||||
* updated. (false doesn't mean that the header should be updated by the
|
* updated. (false doesn't mean that the header should be updated by the
|
||||||
* caller, it just means that updating was not needed or the image cannot be
|
* caller, it just means that updating was not needed or the image cannot be
|
||||||
* written to).
|
* written to).
|
||||||
* On failure the function returns false.
|
* On failure the function returns false.
|
||||||
*/
|
*/
|
||||||
bool qcow2_load_autoloading_dirty_bitmaps(BlockDriverState *bs, Error **errp)
|
bool qcow2_load_dirty_bitmaps(BlockDriverState *bs, Error **errp)
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
Qcow2BitmapList *bm_list;
|
Qcow2BitmapList *bm_list;
|
||||||
|
@ -960,14 +960,16 @@ bool qcow2_load_autoloading_dirty_bitmaps(BlockDriverState *bs, Error **errp)
|
||||||
}
|
}
|
||||||
|
|
||||||
QSIMPLEQ_FOREACH(bm, bm_list, entry) {
|
QSIMPLEQ_FOREACH(bm, bm_list, entry) {
|
||||||
if ((bm->flags & BME_FLAG_AUTO) && !(bm->flags & BME_FLAG_IN_USE)) {
|
if (!(bm->flags & BME_FLAG_IN_USE)) {
|
||||||
BdrvDirtyBitmap *bitmap = load_bitmap(bs, bm, errp);
|
BdrvDirtyBitmap *bitmap = load_bitmap(bs, bm, errp);
|
||||||
if (bitmap == NULL) {
|
if (bitmap == NULL) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(bm->flags & BME_FLAG_AUTO)) {
|
||||||
|
bdrv_disable_dirty_bitmap(bitmap);
|
||||||
|
}
|
||||||
bdrv_dirty_bitmap_set_persistance(bitmap, true);
|
bdrv_dirty_bitmap_set_persistance(bitmap, true);
|
||||||
bdrv_dirty_bitmap_set_autoload(bitmap, true);
|
|
||||||
bm->flags |= BME_FLAG_IN_USE;
|
bm->flags |= BME_FLAG_IN_USE;
|
||||||
created_dirty_bitmaps =
|
created_dirty_bitmaps =
|
||||||
g_slist_append(created_dirty_bitmaps, bitmap);
|
g_slist_append(created_dirty_bitmaps, bitmap);
|
||||||
|
@ -1369,7 +1371,7 @@ void qcow2_store_persistent_dirty_bitmaps(BlockDriverState *bs, Error **errp)
|
||||||
bm->table.size = 0;
|
bm->table.size = 0;
|
||||||
QSIMPLEQ_INSERT_TAIL(&drop_tables, tb, entry);
|
QSIMPLEQ_INSERT_TAIL(&drop_tables, tb, entry);
|
||||||
}
|
}
|
||||||
bm->flags = bdrv_dirty_bitmap_get_autoload(bitmap) ? BME_FLAG_AUTO : 0;
|
bm->flags = bdrv_dirty_bitmap_enabled(bitmap) ? BME_FLAG_AUTO : 0;
|
||||||
bm->granularity_bits = ctz32(bdrv_dirty_bitmap_granularity(bitmap));
|
bm->granularity_bits = ctz32(bdrv_dirty_bitmap_granularity(bitmap));
|
||||||
bm->dirty_bitmap = bitmap;
|
bm->dirty_bitmap = bitmap;
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,26 +39,23 @@ struct Qcow2Cache {
|
||||||
Qcow2CachedTable *entries;
|
Qcow2CachedTable *entries;
|
||||||
struct Qcow2Cache *depends;
|
struct Qcow2Cache *depends;
|
||||||
int size;
|
int size;
|
||||||
|
int table_size;
|
||||||
bool depends_on_flush;
|
bool depends_on_flush;
|
||||||
void *table_array;
|
void *table_array;
|
||||||
uint64_t lru_counter;
|
uint64_t lru_counter;
|
||||||
uint64_t cache_clean_lru_counter;
|
uint64_t cache_clean_lru_counter;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline void *qcow2_cache_get_table_addr(BlockDriverState *bs,
|
static inline void *qcow2_cache_get_table_addr(Qcow2Cache *c, int table)
|
||||||
Qcow2Cache *c, int table)
|
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
return (uint8_t *) c->table_array + (size_t) table * c->table_size;
|
||||||
return (uint8_t *) c->table_array + (size_t) table * s->cluster_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int qcow2_cache_get_table_idx(BlockDriverState *bs,
|
static inline int qcow2_cache_get_table_idx(Qcow2Cache *c, void *table)
|
||||||
Qcow2Cache *c, void *table)
|
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
|
||||||
ptrdiff_t table_offset = (uint8_t *) table - (uint8_t *) c->table_array;
|
ptrdiff_t table_offset = (uint8_t *) table - (uint8_t *) c->table_array;
|
||||||
int idx = table_offset / s->cluster_size;
|
int idx = table_offset / c->table_size;
|
||||||
assert(idx >= 0 && idx < c->size && table_offset % s->cluster_size == 0);
|
assert(idx >= 0 && idx < c->size && table_offset % c->table_size == 0);
|
||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,15 +71,13 @@ static inline const char *qcow2_cache_get_name(BDRVQcow2State *s, Qcow2Cache *c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void qcow2_cache_table_release(BlockDriverState *bs, Qcow2Cache *c,
|
static void qcow2_cache_table_release(Qcow2Cache *c, int i, int num_tables)
|
||||||
int i, int num_tables)
|
|
||||||
{
|
{
|
||||||
/* Using MADV_DONTNEED to discard memory is a Linux-specific feature */
|
/* Using MADV_DONTNEED to discard memory is a Linux-specific feature */
|
||||||
#ifdef CONFIG_LINUX
|
#ifdef CONFIG_LINUX
|
||||||
BDRVQcow2State *s = bs->opaque;
|
void *t = qcow2_cache_get_table_addr(c, i);
|
||||||
void *t = qcow2_cache_get_table_addr(bs, c, i);
|
|
||||||
int align = getpagesize();
|
int align = getpagesize();
|
||||||
size_t mem_size = (size_t) s->cluster_size * num_tables;
|
size_t mem_size = (size_t) c->table_size * num_tables;
|
||||||
size_t offset = QEMU_ALIGN_UP((uintptr_t) t, align) - (uintptr_t) t;
|
size_t offset = QEMU_ALIGN_UP((uintptr_t) t, align) - (uintptr_t) t;
|
||||||
size_t length = QEMU_ALIGN_DOWN(mem_size - offset, align);
|
size_t length = QEMU_ALIGN_DOWN(mem_size - offset, align);
|
||||||
if (mem_size > offset && length > 0) {
|
if (mem_size > offset && length > 0) {
|
||||||
|
@ -98,7 +93,7 @@ static inline bool can_clean_entry(Qcow2Cache *c, int i)
|
||||||
t->lru_counter <= c->cache_clean_lru_counter;
|
t->lru_counter <= c->cache_clean_lru_counter;
|
||||||
}
|
}
|
||||||
|
|
||||||
void qcow2_cache_clean_unused(BlockDriverState *bs, Qcow2Cache *c)
|
void qcow2_cache_clean_unused(Qcow2Cache *c)
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (i < c->size) {
|
while (i < c->size) {
|
||||||
|
@ -118,23 +113,30 @@ void qcow2_cache_clean_unused(BlockDriverState *bs, Qcow2Cache *c)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (to_clean > 0) {
|
if (to_clean > 0) {
|
||||||
qcow2_cache_table_release(bs, c, i - to_clean, to_clean);
|
qcow2_cache_table_release(c, i - to_clean, to_clean);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c->cache_clean_lru_counter = c->lru_counter;
|
c->cache_clean_lru_counter = c->lru_counter;
|
||||||
}
|
}
|
||||||
|
|
||||||
Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables)
|
Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables,
|
||||||
|
unsigned table_size)
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
Qcow2Cache *c;
|
Qcow2Cache *c;
|
||||||
|
|
||||||
|
assert(num_tables > 0);
|
||||||
|
assert(is_power_of_2(table_size));
|
||||||
|
assert(table_size >= (1 << MIN_CLUSTER_BITS));
|
||||||
|
assert(table_size <= s->cluster_size);
|
||||||
|
|
||||||
c = g_new0(Qcow2Cache, 1);
|
c = g_new0(Qcow2Cache, 1);
|
||||||
c->size = num_tables;
|
c->size = num_tables;
|
||||||
|
c->table_size = table_size;
|
||||||
c->entries = g_try_new0(Qcow2CachedTable, num_tables);
|
c->entries = g_try_new0(Qcow2CachedTable, num_tables);
|
||||||
c->table_array = qemu_try_blockalign(bs->file->bs,
|
c->table_array = qemu_try_blockalign(bs->file->bs,
|
||||||
(size_t) num_tables * s->cluster_size);
|
(size_t) num_tables * c->table_size);
|
||||||
|
|
||||||
if (!c->entries || !c->table_array) {
|
if (!c->entries || !c->table_array) {
|
||||||
qemu_vfree(c->table_array);
|
qemu_vfree(c->table_array);
|
||||||
|
@ -146,7 +148,7 @@ Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables)
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
int qcow2_cache_destroy(BlockDriverState *bs, Qcow2Cache *c)
|
int qcow2_cache_destroy(Qcow2Cache *c)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
@ -203,13 +205,13 @@ static int qcow2_cache_entry_flush(BlockDriverState *bs, Qcow2Cache *c, int i)
|
||||||
|
|
||||||
if (c == s->refcount_block_cache) {
|
if (c == s->refcount_block_cache) {
|
||||||
ret = qcow2_pre_write_overlap_check(bs, QCOW2_OL_REFCOUNT_BLOCK,
|
ret = qcow2_pre_write_overlap_check(bs, QCOW2_OL_REFCOUNT_BLOCK,
|
||||||
c->entries[i].offset, s->cluster_size);
|
c->entries[i].offset, c->table_size);
|
||||||
} else if (c == s->l2_table_cache) {
|
} else if (c == s->l2_table_cache) {
|
||||||
ret = qcow2_pre_write_overlap_check(bs, QCOW2_OL_ACTIVE_L2,
|
ret = qcow2_pre_write_overlap_check(bs, QCOW2_OL_ACTIVE_L2,
|
||||||
c->entries[i].offset, s->cluster_size);
|
c->entries[i].offset, c->table_size);
|
||||||
} else {
|
} else {
|
||||||
ret = qcow2_pre_write_overlap_check(bs, 0,
|
ret = qcow2_pre_write_overlap_check(bs, 0,
|
||||||
c->entries[i].offset, s->cluster_size);
|
c->entries[i].offset, c->table_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
@ -223,7 +225,7 @@ static int qcow2_cache_entry_flush(BlockDriverState *bs, Qcow2Cache *c, int i)
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = bdrv_pwrite(bs->file, c->entries[i].offset,
|
ret = bdrv_pwrite(bs->file, c->entries[i].offset,
|
||||||
qcow2_cache_get_table_addr(bs, c, i), s->cluster_size);
|
qcow2_cache_get_table_addr(c, i), c->table_size);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -309,7 +311,7 @@ int qcow2_cache_empty(BlockDriverState *bs, Qcow2Cache *c)
|
||||||
c->entries[i].lru_counter = 0;
|
c->entries[i].lru_counter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
qcow2_cache_table_release(bs, c, 0, c->size);
|
qcow2_cache_table_release(c, 0, c->size);
|
||||||
|
|
||||||
c->lru_counter = 0;
|
c->lru_counter = 0;
|
||||||
|
|
||||||
|
@ -331,7 +333,7 @@ static int qcow2_cache_do_get(BlockDriverState *bs, Qcow2Cache *c,
|
||||||
trace_qcow2_cache_get(qemu_coroutine_self(), c == s->l2_table_cache,
|
trace_qcow2_cache_get(qemu_coroutine_self(), c == s->l2_table_cache,
|
||||||
offset, read_from_disk);
|
offset, read_from_disk);
|
||||||
|
|
||||||
if (offset_into_cluster(s, offset)) {
|
if (!QEMU_IS_ALIGNED(offset, c->table_size)) {
|
||||||
qcow2_signal_corruption(bs, true, -1, -1, "Cannot get entry from %s "
|
qcow2_signal_corruption(bs, true, -1, -1, "Cannot get entry from %s "
|
||||||
"cache: Offset %#" PRIx64 " is unaligned",
|
"cache: Offset %#" PRIx64 " is unaligned",
|
||||||
qcow2_cache_get_name(s, c), offset);
|
qcow2_cache_get_name(s, c), offset);
|
||||||
|
@ -339,7 +341,7 @@ static int qcow2_cache_do_get(BlockDriverState *bs, Qcow2Cache *c,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if the table is already cached */
|
/* Check if the table is already cached */
|
||||||
i = lookup_index = (offset / s->cluster_size * 4) % c->size;
|
i = lookup_index = (offset / c->table_size * 4) % c->size;
|
||||||
do {
|
do {
|
||||||
const Qcow2CachedTable *t = &c->entries[i];
|
const Qcow2CachedTable *t = &c->entries[i];
|
||||||
if (t->offset == offset) {
|
if (t->offset == offset) {
|
||||||
|
@ -379,8 +381,8 @@ static int qcow2_cache_do_get(BlockDriverState *bs, Qcow2Cache *c,
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = bdrv_pread(bs->file, offset,
|
ret = bdrv_pread(bs->file, offset,
|
||||||
qcow2_cache_get_table_addr(bs, c, i),
|
qcow2_cache_get_table_addr(c, i),
|
||||||
s->cluster_size);
|
c->table_size);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -391,7 +393,7 @@ static int qcow2_cache_do_get(BlockDriverState *bs, Qcow2Cache *c,
|
||||||
/* And return the right table */
|
/* And return the right table */
|
||||||
found:
|
found:
|
||||||
c->entries[i].ref++;
|
c->entries[i].ref++;
|
||||||
*table = qcow2_cache_get_table_addr(bs, c, i);
|
*table = qcow2_cache_get_table_addr(c, i);
|
||||||
|
|
||||||
trace_qcow2_cache_get_done(qemu_coroutine_self(),
|
trace_qcow2_cache_get_done(qemu_coroutine_self(),
|
||||||
c == s->l2_table_cache, i);
|
c == s->l2_table_cache, i);
|
||||||
|
@ -411,9 +413,9 @@ int qcow2_cache_get_empty(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset,
|
||||||
return qcow2_cache_do_get(bs, c, offset, table, false);
|
return qcow2_cache_do_get(bs, c, offset, table, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void qcow2_cache_put(BlockDriverState *bs, Qcow2Cache *c, void **table)
|
void qcow2_cache_put(Qcow2Cache *c, void **table)
|
||||||
{
|
{
|
||||||
int i = qcow2_cache_get_table_idx(bs, c, *table);
|
int i = qcow2_cache_get_table_idx(c, *table);
|
||||||
|
|
||||||
c->entries[i].ref--;
|
c->entries[i].ref--;
|
||||||
*table = NULL;
|
*table = NULL;
|
||||||
|
@ -425,30 +427,28 @@ void qcow2_cache_put(BlockDriverState *bs, Qcow2Cache *c, void **table)
|
||||||
assert(c->entries[i].ref >= 0);
|
assert(c->entries[i].ref >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void qcow2_cache_entry_mark_dirty(BlockDriverState *bs, Qcow2Cache *c,
|
void qcow2_cache_entry_mark_dirty(Qcow2Cache *c, void *table)
|
||||||
void *table)
|
|
||||||
{
|
{
|
||||||
int i = qcow2_cache_get_table_idx(bs, c, table);
|
int i = qcow2_cache_get_table_idx(c, table);
|
||||||
assert(c->entries[i].offset != 0);
|
assert(c->entries[i].offset != 0);
|
||||||
c->entries[i].dirty = true;
|
c->entries[i].dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *qcow2_cache_is_table_offset(BlockDriverState *bs, Qcow2Cache *c,
|
void *qcow2_cache_is_table_offset(Qcow2Cache *c, uint64_t offset)
|
||||||
uint64_t offset)
|
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < c->size; i++) {
|
for (i = 0; i < c->size; i++) {
|
||||||
if (c->entries[i].offset == offset) {
|
if (c->entries[i].offset == offset) {
|
||||||
return qcow2_cache_get_table_addr(bs, c, i);
|
return qcow2_cache_get_table_addr(c, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void qcow2_cache_discard(BlockDriverState *bs, Qcow2Cache *c, void *table)
|
void qcow2_cache_discard(Qcow2Cache *c, void *table)
|
||||||
{
|
{
|
||||||
int i = qcow2_cache_get_table_idx(bs, c, table);
|
int i = qcow2_cache_get_table_idx(c, table);
|
||||||
|
|
||||||
assert(c->entries[i].ref == 0);
|
assert(c->entries[i].ref == 0);
|
||||||
|
|
||||||
|
@ -456,5 +456,5 @@ void qcow2_cache_discard(BlockDriverState *bs, Qcow2Cache *c, void *table)
|
||||||
c->entries[i].lru_counter = 0;
|
c->entries[i].lru_counter = 0;
|
||||||
c->entries[i].dirty = false;
|
c->entries[i].dirty = false;
|
||||||
|
|
||||||
qcow2_cache_table_release(bs, c, i, 1);
|
qcow2_cache_table_release(c, i, 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,20 +195,26 @@ int qcow2_grow_l1_table(BlockDriverState *bs, uint64_t min_size,
|
||||||
/*
|
/*
|
||||||
* l2_load
|
* l2_load
|
||||||
*
|
*
|
||||||
* Loads a L2 table into memory. If the table is in the cache, the cache
|
* @bs: The BlockDriverState
|
||||||
* is used; otherwise the L2 table is loaded from the image file.
|
* @offset: A guest offset, used to calculate what slice of the L2
|
||||||
|
* table to load.
|
||||||
|
* @l2_offset: Offset to the L2 table in the image file.
|
||||||
|
* @l2_slice: Location to store the pointer to the L2 slice.
|
||||||
*
|
*
|
||||||
* Returns a pointer to the L2 table on success, or NULL if the read from
|
* Loads a L2 slice into memory (L2 slices are the parts of L2 tables
|
||||||
* the image file failed.
|
* that are loaded by the qcow2 cache). If the slice is in the cache,
|
||||||
|
* the cache is used; otherwise the L2 slice is loaded from the image
|
||||||
|
* file.
|
||||||
*/
|
*/
|
||||||
|
static int l2_load(BlockDriverState *bs, uint64_t offset,
|
||||||
static int l2_load(BlockDriverState *bs, uint64_t l2_offset,
|
uint64_t l2_offset, uint64_t **l2_slice)
|
||||||
uint64_t **l2_table)
|
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
|
int start_of_slice = sizeof(uint64_t) *
|
||||||
|
(offset_to_l2_index(s, offset) - offset_to_l2_slice_index(s, offset));
|
||||||
|
|
||||||
return qcow2_cache_get(bs, s->l2_table_cache, l2_offset,
|
return qcow2_cache_get(bs, s->l2_table_cache, l2_offset + start_of_slice,
|
||||||
(void **)l2_table);
|
(void **)l2_slice);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -257,11 +263,12 @@ int qcow2_write_l1_entry(BlockDriverState *bs, int l1_index)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static int l2_allocate(BlockDriverState *bs, int l1_index, uint64_t **table)
|
static int l2_allocate(BlockDriverState *bs, int l1_index)
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
uint64_t old_l2_offset;
|
uint64_t old_l2_offset;
|
||||||
uint64_t *l2_table = NULL;
|
uint64_t *l2_slice = NULL;
|
||||||
|
unsigned slice, slice_size2, n_slices;
|
||||||
int64_t l2_offset;
|
int64_t l2_offset;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
@ -292,39 +299,47 @@ static int l2_allocate(BlockDriverState *bs, int l1_index, uint64_t **table)
|
||||||
|
|
||||||
/* allocate a new entry in the l2 cache */
|
/* allocate a new entry in the l2 cache */
|
||||||
|
|
||||||
|
slice_size2 = s->l2_slice_size * sizeof(uint64_t);
|
||||||
|
n_slices = s->cluster_size / slice_size2;
|
||||||
|
|
||||||
trace_qcow2_l2_allocate_get_empty(bs, l1_index);
|
trace_qcow2_l2_allocate_get_empty(bs, l1_index);
|
||||||
ret = qcow2_cache_get_empty(bs, s->l2_table_cache, l2_offset, (void**) table);
|
for (slice = 0; slice < n_slices; slice++) {
|
||||||
|
ret = qcow2_cache_get_empty(bs, s->l2_table_cache,
|
||||||
|
l2_offset + slice * slice_size2,
|
||||||
|
(void **) &l2_slice);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
l2_table = *table;
|
|
||||||
|
|
||||||
if ((old_l2_offset & L1E_OFFSET_MASK) == 0) {
|
if ((old_l2_offset & L1E_OFFSET_MASK) == 0) {
|
||||||
/* if there was no old l2 table, clear the new table */
|
/* if there was no old l2 table, clear the new slice */
|
||||||
memset(l2_table, 0, s->l2_size * sizeof(uint64_t));
|
memset(l2_slice, 0, slice_size2);
|
||||||
} else {
|
} else {
|
||||||
uint64_t* old_table;
|
uint64_t *old_slice;
|
||||||
|
uint64_t old_l2_slice_offset =
|
||||||
|
(old_l2_offset & L1E_OFFSET_MASK) + slice * slice_size2;
|
||||||
|
|
||||||
/* if there was an old l2 table, read it from the disk */
|
/* if there was an old l2 table, read a slice from the disk */
|
||||||
BLKDBG_EVENT(bs->file, BLKDBG_L2_ALLOC_COW_READ);
|
BLKDBG_EVENT(bs->file, BLKDBG_L2_ALLOC_COW_READ);
|
||||||
ret = qcow2_cache_get(bs, s->l2_table_cache,
|
ret = qcow2_cache_get(bs, s->l2_table_cache, old_l2_slice_offset,
|
||||||
old_l2_offset & L1E_OFFSET_MASK,
|
(void **) &old_slice);
|
||||||
(void**) &old_table);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(l2_table, old_table, s->cluster_size);
|
memcpy(l2_slice, old_slice, slice_size2);
|
||||||
|
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void **) &old_table);
|
qcow2_cache_put(s->l2_table_cache, (void **) &old_slice);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* write the l2 table to the file */
|
/* write the l2 slice to the file */
|
||||||
BLKDBG_EVENT(bs->file, BLKDBG_L2_ALLOC_WRITE);
|
BLKDBG_EVENT(bs->file, BLKDBG_L2_ALLOC_WRITE);
|
||||||
|
|
||||||
trace_qcow2_l2_allocate_write_l2(bs, l1_index);
|
trace_qcow2_l2_allocate_write_l2(bs, l1_index);
|
||||||
qcow2_cache_entry_mark_dirty(bs, s->l2_table_cache, l2_table);
|
qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_slice);
|
||||||
|
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||||
|
}
|
||||||
|
|
||||||
ret = qcow2_cache_flush(bs, s->l2_table_cache);
|
ret = qcow2_cache_flush(bs, s->l2_table_cache);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
|
@ -338,14 +353,13 @@ static int l2_allocate(BlockDriverState *bs, int l1_index, uint64_t **table)
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
*table = l2_table;
|
|
||||||
trace_qcow2_l2_allocate_done(bs, l1_index, 0);
|
trace_qcow2_l2_allocate_done(bs, l1_index, 0);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
trace_qcow2_l2_allocate_done(bs, l1_index, ret);
|
trace_qcow2_l2_allocate_done(bs, l1_index, ret);
|
||||||
if (l2_table != NULL) {
|
if (l2_slice != NULL) {
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void**) table);
|
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||||
}
|
}
|
||||||
s->l1_table[l1_index] = old_l2_offset;
|
s->l1_table[l1_index] = old_l2_offset;
|
||||||
if (l2_offset > 0) {
|
if (l2_offset > 0) {
|
||||||
|
@ -356,19 +370,19 @@ fail:
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Checks how many clusters in a given L2 table are contiguous in the image
|
* Checks how many clusters in a given L2 slice are contiguous in the image
|
||||||
* file. As soon as one of the flags in the bitmask stop_flags changes compared
|
* file. As soon as one of the flags in the bitmask stop_flags changes compared
|
||||||
* to the first cluster, the search is stopped and the cluster is not counted
|
* to the first cluster, the search is stopped and the cluster is not counted
|
||||||
* as contiguous. (This allows it, for example, to stop at the first compressed
|
* as contiguous. (This allows it, for example, to stop at the first compressed
|
||||||
* cluster which may require a different handling)
|
* cluster which may require a different handling)
|
||||||
*/
|
*/
|
||||||
static int count_contiguous_clusters(int nb_clusters, int cluster_size,
|
static int count_contiguous_clusters(int nb_clusters, int cluster_size,
|
||||||
uint64_t *l2_table, uint64_t stop_flags)
|
uint64_t *l2_slice, uint64_t stop_flags)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
QCow2ClusterType first_cluster_type;
|
QCow2ClusterType first_cluster_type;
|
||||||
uint64_t mask = stop_flags | L2E_OFFSET_MASK | QCOW_OFLAG_COMPRESSED;
|
uint64_t mask = stop_flags | L2E_OFFSET_MASK | QCOW_OFLAG_COMPRESSED;
|
||||||
uint64_t first_entry = be64_to_cpu(l2_table[0]);
|
uint64_t first_entry = be64_to_cpu(l2_slice[0]);
|
||||||
uint64_t offset = first_entry & mask;
|
uint64_t offset = first_entry & mask;
|
||||||
|
|
||||||
if (!offset) {
|
if (!offset) {
|
||||||
|
@ -381,7 +395,7 @@ static int count_contiguous_clusters(int nb_clusters, int cluster_size,
|
||||||
first_cluster_type == QCOW2_CLUSTER_ZERO_ALLOC);
|
first_cluster_type == QCOW2_CLUSTER_ZERO_ALLOC);
|
||||||
|
|
||||||
for (i = 0; i < nb_clusters; i++) {
|
for (i = 0; i < nb_clusters; i++) {
|
||||||
uint64_t l2_entry = be64_to_cpu(l2_table[i]) & mask;
|
uint64_t l2_entry = be64_to_cpu(l2_slice[i]) & mask;
|
||||||
if (offset + (uint64_t) i * cluster_size != l2_entry) {
|
if (offset + (uint64_t) i * cluster_size != l2_entry) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -392,10 +406,10 @@ static int count_contiguous_clusters(int nb_clusters, int cluster_size,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Checks how many consecutive unallocated clusters in a given L2
|
* Checks how many consecutive unallocated clusters in a given L2
|
||||||
* table have the same cluster type.
|
* slice have the same cluster type.
|
||||||
*/
|
*/
|
||||||
static int count_contiguous_clusters_unallocated(int nb_clusters,
|
static int count_contiguous_clusters_unallocated(int nb_clusters,
|
||||||
uint64_t *l2_table,
|
uint64_t *l2_slice,
|
||||||
QCow2ClusterType wanted_type)
|
QCow2ClusterType wanted_type)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
@ -403,7 +417,7 @@ static int count_contiguous_clusters_unallocated(int nb_clusters,
|
||||||
assert(wanted_type == QCOW2_CLUSTER_ZERO_PLAIN ||
|
assert(wanted_type == QCOW2_CLUSTER_ZERO_PLAIN ||
|
||||||
wanted_type == QCOW2_CLUSTER_UNALLOCATED);
|
wanted_type == QCOW2_CLUSTER_UNALLOCATED);
|
||||||
for (i = 0; i < nb_clusters; i++) {
|
for (i = 0; i < nb_clusters; i++) {
|
||||||
uint64_t entry = be64_to_cpu(l2_table[i]);
|
uint64_t entry = be64_to_cpu(l2_slice[i]);
|
||||||
QCow2ClusterType type = qcow2_get_cluster_type(entry);
|
QCow2ClusterType type = qcow2_get_cluster_type(entry);
|
||||||
|
|
||||||
if (type != wanted_type) {
|
if (type != wanted_type) {
|
||||||
|
@ -515,8 +529,8 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
unsigned int l2_index;
|
unsigned int l2_index;
|
||||||
uint64_t l1_index, l2_offset, *l2_table;
|
uint64_t l1_index, l2_offset, *l2_slice;
|
||||||
int l1_bits, c;
|
int c;
|
||||||
unsigned int offset_in_cluster;
|
unsigned int offset_in_cluster;
|
||||||
uint64_t bytes_available, bytes_needed, nb_clusters;
|
uint64_t bytes_available, bytes_needed, nb_clusters;
|
||||||
QCow2ClusterType type;
|
QCow2ClusterType type;
|
||||||
|
@ -525,12 +539,12 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
||||||
offset_in_cluster = offset_into_cluster(s, offset);
|
offset_in_cluster = offset_into_cluster(s, offset);
|
||||||
bytes_needed = (uint64_t) *bytes + offset_in_cluster;
|
bytes_needed = (uint64_t) *bytes + offset_in_cluster;
|
||||||
|
|
||||||
l1_bits = s->l2_bits + s->cluster_bits;
|
|
||||||
|
|
||||||
/* compute how many bytes there are between the start of the cluster
|
/* compute how many bytes there are between the start of the cluster
|
||||||
* containing offset and the end of the l1 entry */
|
* containing offset and the end of the l2 slice that contains
|
||||||
bytes_available = (1ULL << l1_bits) - (offset & ((1ULL << l1_bits) - 1))
|
* the entry pointing to it */
|
||||||
+ offset_in_cluster;
|
bytes_available =
|
||||||
|
((uint64_t) (s->l2_slice_size - offset_to_l2_slice_index(s, offset)))
|
||||||
|
<< s->cluster_bits;
|
||||||
|
|
||||||
if (bytes_needed > bytes_available) {
|
if (bytes_needed > bytes_available) {
|
||||||
bytes_needed = bytes_available;
|
bytes_needed = bytes_available;
|
||||||
|
@ -540,7 +554,7 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
||||||
|
|
||||||
/* seek to the l2 offset in the l1 table */
|
/* seek to the l2 offset in the l1 table */
|
||||||
|
|
||||||
l1_index = offset >> l1_bits;
|
l1_index = offset_to_l1_index(s, offset);
|
||||||
if (l1_index >= s->l1_size) {
|
if (l1_index >= s->l1_size) {
|
||||||
type = QCOW2_CLUSTER_UNALLOCATED;
|
type = QCOW2_CLUSTER_UNALLOCATED;
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -559,17 +573,17 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
||||||
return -EIO;
|
return -EIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* load the l2 table in memory */
|
/* load the l2 slice in memory */
|
||||||
|
|
||||||
ret = l2_load(bs, l2_offset, &l2_table);
|
ret = l2_load(bs, offset, l2_offset, &l2_slice);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* find the cluster offset for the given disk offset */
|
/* find the cluster offset for the given disk offset */
|
||||||
|
|
||||||
l2_index = offset_to_l2_index(s, offset);
|
l2_index = offset_to_l2_slice_index(s, offset);
|
||||||
*cluster_offset = be64_to_cpu(l2_table[l2_index]);
|
*cluster_offset = be64_to_cpu(l2_slice[l2_index]);
|
||||||
|
|
||||||
nb_clusters = size_to_clusters(s, bytes_needed);
|
nb_clusters = size_to_clusters(s, bytes_needed);
|
||||||
/* bytes_needed <= *bytes + offset_in_cluster, both of which are unsigned
|
/* bytes_needed <= *bytes + offset_in_cluster, both of which are unsigned
|
||||||
|
@ -596,14 +610,14 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
||||||
case QCOW2_CLUSTER_UNALLOCATED:
|
case QCOW2_CLUSTER_UNALLOCATED:
|
||||||
/* how many empty clusters ? */
|
/* how many empty clusters ? */
|
||||||
c = count_contiguous_clusters_unallocated(nb_clusters,
|
c = count_contiguous_clusters_unallocated(nb_clusters,
|
||||||
&l2_table[l2_index], type);
|
&l2_slice[l2_index], type);
|
||||||
*cluster_offset = 0;
|
*cluster_offset = 0;
|
||||||
break;
|
break;
|
||||||
case QCOW2_CLUSTER_ZERO_ALLOC:
|
case QCOW2_CLUSTER_ZERO_ALLOC:
|
||||||
case QCOW2_CLUSTER_NORMAL:
|
case QCOW2_CLUSTER_NORMAL:
|
||||||
/* how many allocated clusters ? */
|
/* how many allocated clusters ? */
|
||||||
c = count_contiguous_clusters(nb_clusters, s->cluster_size,
|
c = count_contiguous_clusters(nb_clusters, s->cluster_size,
|
||||||
&l2_table[l2_index], QCOW_OFLAG_ZERO);
|
&l2_slice[l2_index], QCOW_OFLAG_ZERO);
|
||||||
*cluster_offset &= L2E_OFFSET_MASK;
|
*cluster_offset &= L2E_OFFSET_MASK;
|
||||||
if (offset_into_cluster(s, *cluster_offset)) {
|
if (offset_into_cluster(s, *cluster_offset)) {
|
||||||
qcow2_signal_corruption(bs, true, -1, -1,
|
qcow2_signal_corruption(bs, true, -1, -1,
|
||||||
|
@ -619,7 +633,7 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table);
|
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||||
|
|
||||||
bytes_available = (int64_t)c * s->cluster_size;
|
bytes_available = (int64_t)c * s->cluster_size;
|
||||||
|
|
||||||
|
@ -637,7 +651,7 @@ out:
|
||||||
return type;
|
return type;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void **)&l2_table);
|
qcow2_cache_put(s->l2_table_cache, (void **)&l2_slice);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -645,26 +659,25 @@ fail:
|
||||||
* get_cluster_table
|
* get_cluster_table
|
||||||
*
|
*
|
||||||
* for a given disk offset, load (and allocate if needed)
|
* for a given disk offset, load (and allocate if needed)
|
||||||
* the l2 table.
|
* the appropriate slice of its l2 table.
|
||||||
*
|
*
|
||||||
* the l2 table offset in the qcow2 file and the cluster index
|
* the cluster index in the l2 slice is given to the caller.
|
||||||
* in the l2 table are given to the caller.
|
|
||||||
*
|
*
|
||||||
* Returns 0 on success, -errno in failure case
|
* Returns 0 on success, -errno in failure case
|
||||||
*/
|
*/
|
||||||
static int get_cluster_table(BlockDriverState *bs, uint64_t offset,
|
static int get_cluster_table(BlockDriverState *bs, uint64_t offset,
|
||||||
uint64_t **new_l2_table,
|
uint64_t **new_l2_slice,
|
||||||
int *new_l2_index)
|
int *new_l2_index)
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
unsigned int l2_index;
|
unsigned int l2_index;
|
||||||
uint64_t l1_index, l2_offset;
|
uint64_t l1_index, l2_offset;
|
||||||
uint64_t *l2_table = NULL;
|
uint64_t *l2_slice = NULL;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/* seek to the l2 offset in the l1 table */
|
/* seek to the l2 offset in the l1 table */
|
||||||
|
|
||||||
l1_index = offset >> (s->l2_bits + s->cluster_bits);
|
l1_index = offset_to_l1_index(s, offset);
|
||||||
if (l1_index >= s->l1_size) {
|
if (l1_index >= s->l1_size) {
|
||||||
ret = qcow2_grow_l1_table(bs, l1_index + 1, false);
|
ret = qcow2_grow_l1_table(bs, l1_index + 1, false);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
@ -681,17 +694,9 @@ static int get_cluster_table(BlockDriverState *bs, uint64_t offset,
|
||||||
return -EIO;
|
return -EIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* seek the l2 table of the given l2 offset */
|
if (!(s->l1_table[l1_index] & QCOW_OFLAG_COPIED)) {
|
||||||
|
|
||||||
if (s->l1_table[l1_index] & QCOW_OFLAG_COPIED) {
|
|
||||||
/* load the l2 table in memory */
|
|
||||||
ret = l2_load(bs, l2_offset, &l2_table);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* First allocate a new L2 table (and do COW if needed) */
|
/* First allocate a new L2 table (and do COW if needed) */
|
||||||
ret = l2_allocate(bs, l1_index, &l2_table);
|
ret = l2_allocate(bs, l1_index);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -701,13 +706,23 @@ static int get_cluster_table(BlockDriverState *bs, uint64_t offset,
|
||||||
qcow2_free_clusters(bs, l2_offset, s->l2_size * sizeof(uint64_t),
|
qcow2_free_clusters(bs, l2_offset, s->l2_size * sizeof(uint64_t),
|
||||||
QCOW2_DISCARD_OTHER);
|
QCOW2_DISCARD_OTHER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Get the offset of the newly-allocated l2 table */
|
||||||
|
l2_offset = s->l1_table[l1_index] & L1E_OFFSET_MASK;
|
||||||
|
assert(offset_into_cluster(s, l2_offset) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* load the l2 slice in memory */
|
||||||
|
ret = l2_load(bs, offset, l2_offset, &l2_slice);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* find the cluster offset for the given disk offset */
|
/* find the cluster offset for the given disk offset */
|
||||||
|
|
||||||
l2_index = offset_to_l2_index(s, offset);
|
l2_index = offset_to_l2_slice_index(s, offset);
|
||||||
|
|
||||||
*new_l2_table = l2_table;
|
*new_l2_slice = l2_slice;
|
||||||
*new_l2_index = l2_index;
|
*new_l2_index = l2_index;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -732,26 +747,26 @@ uint64_t qcow2_alloc_compressed_cluster_offset(BlockDriverState *bs,
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
int l2_index, ret;
|
int l2_index, ret;
|
||||||
uint64_t *l2_table;
|
uint64_t *l2_slice;
|
||||||
int64_t cluster_offset;
|
int64_t cluster_offset;
|
||||||
int nb_csectors;
|
int nb_csectors;
|
||||||
|
|
||||||
ret = get_cluster_table(bs, offset, &l2_table, &l2_index);
|
ret = get_cluster_table(bs, offset, &l2_slice, &l2_index);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compression can't overwrite anything. Fail if the cluster was already
|
/* Compression can't overwrite anything. Fail if the cluster was already
|
||||||
* allocated. */
|
* allocated. */
|
||||||
cluster_offset = be64_to_cpu(l2_table[l2_index]);
|
cluster_offset = be64_to_cpu(l2_slice[l2_index]);
|
||||||
if (cluster_offset & L2E_OFFSET_MASK) {
|
if (cluster_offset & L2E_OFFSET_MASK) {
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table);
|
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
cluster_offset = qcow2_alloc_bytes(bs, compressed_size);
|
cluster_offset = qcow2_alloc_bytes(bs, compressed_size);
|
||||||
if (cluster_offset < 0) {
|
if (cluster_offset < 0) {
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table);
|
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -766,9 +781,9 @@ uint64_t qcow2_alloc_compressed_cluster_offset(BlockDriverState *bs,
|
||||||
/* compressed clusters never have the copied flag */
|
/* compressed clusters never have the copied flag */
|
||||||
|
|
||||||
BLKDBG_EVENT(bs->file, BLKDBG_L2_UPDATE_COMPRESSED);
|
BLKDBG_EVENT(bs->file, BLKDBG_L2_UPDATE_COMPRESSED);
|
||||||
qcow2_cache_entry_mark_dirty(bs, s->l2_table_cache, l2_table);
|
qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_slice);
|
||||||
l2_table[l2_index] = cpu_to_be64(cluster_offset);
|
l2_slice[l2_index] = cpu_to_be64(cluster_offset);
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void **) &l2_table);
|
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||||
|
|
||||||
return cluster_offset;
|
return cluster_offset;
|
||||||
}
|
}
|
||||||
|
@ -907,7 +922,7 @@ int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m)
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
int i, j = 0, l2_index, ret;
|
int i, j = 0, l2_index, ret;
|
||||||
uint64_t *old_cluster, *l2_table;
|
uint64_t *old_cluster, *l2_slice;
|
||||||
uint64_t cluster_offset = m->alloc_offset;
|
uint64_t cluster_offset = m->alloc_offset;
|
||||||
|
|
||||||
trace_qcow2_cluster_link_l2(qemu_coroutine_self(), m->nb_clusters);
|
trace_qcow2_cluster_link_l2(qemu_coroutine_self(), m->nb_clusters);
|
||||||
|
@ -934,13 +949,13 @@ int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m)
|
||||||
s->refcount_block_cache);
|
s->refcount_block_cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = get_cluster_table(bs, m->offset, &l2_table, &l2_index);
|
ret = get_cluster_table(bs, m->offset, &l2_slice, &l2_index);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
qcow2_cache_entry_mark_dirty(bs, s->l2_table_cache, l2_table);
|
qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_slice);
|
||||||
|
|
||||||
assert(l2_index + m->nb_clusters <= s->l2_size);
|
assert(l2_index + m->nb_clusters <= s->l2_slice_size);
|
||||||
for (i = 0; i < m->nb_clusters; i++) {
|
for (i = 0; i < m->nb_clusters; i++) {
|
||||||
/* if two concurrent writes happen to the same unallocated cluster
|
/* if two concurrent writes happen to the same unallocated cluster
|
||||||
* each write allocates separate cluster and writes data concurrently.
|
* each write allocates separate cluster and writes data concurrently.
|
||||||
|
@ -948,16 +963,16 @@ int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m)
|
||||||
* cluster the second one has to do RMW (which is done above by
|
* cluster the second one has to do RMW (which is done above by
|
||||||
* perform_cow()), update l2 table with its cluster pointer and free
|
* perform_cow()), update l2 table with its cluster pointer and free
|
||||||
* old cluster. This is what this loop does */
|
* old cluster. This is what this loop does */
|
||||||
if (l2_table[l2_index + i] != 0) {
|
if (l2_slice[l2_index + i] != 0) {
|
||||||
old_cluster[j++] = l2_table[l2_index + i];
|
old_cluster[j++] = l2_slice[l2_index + i];
|
||||||
}
|
}
|
||||||
|
|
||||||
l2_table[l2_index + i] = cpu_to_be64((cluster_offset +
|
l2_slice[l2_index + i] = cpu_to_be64((cluster_offset +
|
||||||
(i << s->cluster_bits)) | QCOW_OFLAG_COPIED);
|
(i << s->cluster_bits)) | QCOW_OFLAG_COPIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void **) &l2_table);
|
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If this was a COW, we need to decrease the refcount of the old cluster.
|
* If this was a COW, we need to decrease the refcount of the old cluster.
|
||||||
|
@ -984,12 +999,12 @@ err:
|
||||||
* which must copy from the backing file)
|
* which must copy from the backing file)
|
||||||
*/
|
*/
|
||||||
static int count_cow_clusters(BDRVQcow2State *s, int nb_clusters,
|
static int count_cow_clusters(BDRVQcow2State *s, int nb_clusters,
|
||||||
uint64_t *l2_table, int l2_index)
|
uint64_t *l2_slice, int l2_index)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < nb_clusters; i++) {
|
for (i = 0; i < nb_clusters; i++) {
|
||||||
uint64_t l2_entry = be64_to_cpu(l2_table[l2_index + i]);
|
uint64_t l2_entry = be64_to_cpu(l2_slice[l2_index + i]);
|
||||||
QCow2ClusterType cluster_type = qcow2_get_cluster_type(l2_entry);
|
QCow2ClusterType cluster_type = qcow2_get_cluster_type(l2_entry);
|
||||||
|
|
||||||
switch(cluster_type) {
|
switch(cluster_type) {
|
||||||
|
@ -1104,7 +1119,7 @@ static int handle_copied(BlockDriverState *bs, uint64_t guest_offset,
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
int l2_index;
|
int l2_index;
|
||||||
uint64_t cluster_offset;
|
uint64_t cluster_offset;
|
||||||
uint64_t *l2_table;
|
uint64_t *l2_slice;
|
||||||
uint64_t nb_clusters;
|
uint64_t nb_clusters;
|
||||||
unsigned int keep_clusters;
|
unsigned int keep_clusters;
|
||||||
int ret;
|
int ret;
|
||||||
|
@ -1116,23 +1131,23 @@ static int handle_copied(BlockDriverState *bs, uint64_t guest_offset,
|
||||||
== offset_into_cluster(s, *host_offset));
|
== offset_into_cluster(s, *host_offset));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Calculate the number of clusters to look for. We stop at L2 table
|
* Calculate the number of clusters to look for. We stop at L2 slice
|
||||||
* boundaries to keep things simple.
|
* boundaries to keep things simple.
|
||||||
*/
|
*/
|
||||||
nb_clusters =
|
nb_clusters =
|
||||||
size_to_clusters(s, offset_into_cluster(s, guest_offset) + *bytes);
|
size_to_clusters(s, offset_into_cluster(s, guest_offset) + *bytes);
|
||||||
|
|
||||||
l2_index = offset_to_l2_index(s, guest_offset);
|
l2_index = offset_to_l2_slice_index(s, guest_offset);
|
||||||
nb_clusters = MIN(nb_clusters, s->l2_size - l2_index);
|
nb_clusters = MIN(nb_clusters, s->l2_slice_size - l2_index);
|
||||||
assert(nb_clusters <= INT_MAX);
|
assert(nb_clusters <= INT_MAX);
|
||||||
|
|
||||||
/* Find L2 entry for the first involved cluster */
|
/* Find L2 entry for the first involved cluster */
|
||||||
ret = get_cluster_table(bs, guest_offset, &l2_table, &l2_index);
|
ret = get_cluster_table(bs, guest_offset, &l2_slice, &l2_index);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
cluster_offset = be64_to_cpu(l2_table[l2_index]);
|
cluster_offset = be64_to_cpu(l2_slice[l2_index]);
|
||||||
|
|
||||||
/* Check how many clusters are already allocated and don't need COW */
|
/* Check how many clusters are already allocated and don't need COW */
|
||||||
if (qcow2_get_cluster_type(cluster_offset) == QCOW2_CLUSTER_NORMAL
|
if (qcow2_get_cluster_type(cluster_offset) == QCOW2_CLUSTER_NORMAL
|
||||||
|
@ -1160,7 +1175,7 @@ static int handle_copied(BlockDriverState *bs, uint64_t guest_offset,
|
||||||
/* We keep all QCOW_OFLAG_COPIED clusters */
|
/* We keep all QCOW_OFLAG_COPIED clusters */
|
||||||
keep_clusters =
|
keep_clusters =
|
||||||
count_contiguous_clusters(nb_clusters, s->cluster_size,
|
count_contiguous_clusters(nb_clusters, s->cluster_size,
|
||||||
&l2_table[l2_index],
|
&l2_slice[l2_index],
|
||||||
QCOW_OFLAG_COPIED | QCOW_OFLAG_ZERO);
|
QCOW_OFLAG_COPIED | QCOW_OFLAG_ZERO);
|
||||||
assert(keep_clusters <= nb_clusters);
|
assert(keep_clusters <= nb_clusters);
|
||||||
|
|
||||||
|
@ -1175,7 +1190,7 @@ static int handle_copied(BlockDriverState *bs, uint64_t guest_offset,
|
||||||
|
|
||||||
/* Cleanup */
|
/* Cleanup */
|
||||||
out:
|
out:
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void **) &l2_table);
|
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||||
|
|
||||||
/* Only return a host offset if we actually made progress. Otherwise we
|
/* Only return a host offset if we actually made progress. Otherwise we
|
||||||
* would make requirements for handle_alloc() that it can't fulfill */
|
* would make requirements for handle_alloc() that it can't fulfill */
|
||||||
|
@ -1259,7 +1274,7 @@ static int handle_alloc(BlockDriverState *bs, uint64_t guest_offset,
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
int l2_index;
|
int l2_index;
|
||||||
uint64_t *l2_table;
|
uint64_t *l2_slice;
|
||||||
uint64_t entry;
|
uint64_t entry;
|
||||||
uint64_t nb_clusters;
|
uint64_t nb_clusters;
|
||||||
int ret;
|
int ret;
|
||||||
|
@ -1272,29 +1287,29 @@ static int handle_alloc(BlockDriverState *bs, uint64_t guest_offset,
|
||||||
assert(*bytes > 0);
|
assert(*bytes > 0);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Calculate the number of clusters to look for. We stop at L2 table
|
* Calculate the number of clusters to look for. We stop at L2 slice
|
||||||
* boundaries to keep things simple.
|
* boundaries to keep things simple.
|
||||||
*/
|
*/
|
||||||
nb_clusters =
|
nb_clusters =
|
||||||
size_to_clusters(s, offset_into_cluster(s, guest_offset) + *bytes);
|
size_to_clusters(s, offset_into_cluster(s, guest_offset) + *bytes);
|
||||||
|
|
||||||
l2_index = offset_to_l2_index(s, guest_offset);
|
l2_index = offset_to_l2_slice_index(s, guest_offset);
|
||||||
nb_clusters = MIN(nb_clusters, s->l2_size - l2_index);
|
nb_clusters = MIN(nb_clusters, s->l2_slice_size - l2_index);
|
||||||
assert(nb_clusters <= INT_MAX);
|
assert(nb_clusters <= INT_MAX);
|
||||||
|
|
||||||
/* Find L2 entry for the first involved cluster */
|
/* Find L2 entry for the first involved cluster */
|
||||||
ret = get_cluster_table(bs, guest_offset, &l2_table, &l2_index);
|
ret = get_cluster_table(bs, guest_offset, &l2_slice, &l2_index);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
entry = be64_to_cpu(l2_table[l2_index]);
|
entry = be64_to_cpu(l2_slice[l2_index]);
|
||||||
|
|
||||||
/* For the moment, overwrite compressed clusters one by one */
|
/* For the moment, overwrite compressed clusters one by one */
|
||||||
if (entry & QCOW_OFLAG_COMPRESSED) {
|
if (entry & QCOW_OFLAG_COMPRESSED) {
|
||||||
nb_clusters = 1;
|
nb_clusters = 1;
|
||||||
} else {
|
} else {
|
||||||
nb_clusters = count_cow_clusters(s, nb_clusters, l2_table, l2_index);
|
nb_clusters = count_cow_clusters(s, nb_clusters, l2_slice, l2_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function is only called when there were no non-COW clusters, so if
|
/* This function is only called when there were no non-COW clusters, so if
|
||||||
|
@ -1323,7 +1338,7 @@ static int handle_alloc(BlockDriverState *bs, uint64_t guest_offset,
|
||||||
* nb_clusters already to a range of COW clusters */
|
* nb_clusters already to a range of COW clusters */
|
||||||
preallocated_nb_clusters =
|
preallocated_nb_clusters =
|
||||||
count_contiguous_clusters(nb_clusters, s->cluster_size,
|
count_contiguous_clusters(nb_clusters, s->cluster_size,
|
||||||
&l2_table[l2_index], QCOW_OFLAG_COPIED);
|
&l2_slice[l2_index], QCOW_OFLAG_COPIED);
|
||||||
assert(preallocated_nb_clusters > 0);
|
assert(preallocated_nb_clusters > 0);
|
||||||
|
|
||||||
nb_clusters = preallocated_nb_clusters;
|
nb_clusters = preallocated_nb_clusters;
|
||||||
|
@ -1334,7 +1349,7 @@ static int handle_alloc(BlockDriverState *bs, uint64_t guest_offset,
|
||||||
keep_old_clusters = true;
|
keep_old_clusters = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void **) &l2_table);
|
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||||
|
|
||||||
if (!alloc_cluster_offset) {
|
if (!alloc_cluster_offset) {
|
||||||
/* Allocate, if necessary at a given offset in the image file */
|
/* Allocate, if necessary at a given offset in the image file */
|
||||||
|
@ -1616,32 +1631,32 @@ int qcow2_decompress_cluster(BlockDriverState *bs, uint64_t cluster_offset)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This discards as many clusters of nb_clusters as possible at once (i.e.
|
* This discards as many clusters of nb_clusters as possible at once (i.e.
|
||||||
* all clusters in the same L2 table) and returns the number of discarded
|
* all clusters in the same L2 slice) and returns the number of discarded
|
||||||
* clusters.
|
* clusters.
|
||||||
*/
|
*/
|
||||||
static int discard_single_l2(BlockDriverState *bs, uint64_t offset,
|
static int discard_in_l2_slice(BlockDriverState *bs, uint64_t offset,
|
||||||
uint64_t nb_clusters, enum qcow2_discard_type type,
|
uint64_t nb_clusters,
|
||||||
bool full_discard)
|
enum qcow2_discard_type type, bool full_discard)
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
uint64_t *l2_table;
|
uint64_t *l2_slice;
|
||||||
int l2_index;
|
int l2_index;
|
||||||
int ret;
|
int ret;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
ret = get_cluster_table(bs, offset, &l2_table, &l2_index);
|
ret = get_cluster_table(bs, offset, &l2_slice, &l2_index);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Limit nb_clusters to one L2 table */
|
/* Limit nb_clusters to one L2 slice */
|
||||||
nb_clusters = MIN(nb_clusters, s->l2_size - l2_index);
|
nb_clusters = MIN(nb_clusters, s->l2_slice_size - l2_index);
|
||||||
assert(nb_clusters <= INT_MAX);
|
assert(nb_clusters <= INT_MAX);
|
||||||
|
|
||||||
for (i = 0; i < nb_clusters; i++) {
|
for (i = 0; i < nb_clusters; i++) {
|
||||||
uint64_t old_l2_entry;
|
uint64_t old_l2_entry;
|
||||||
|
|
||||||
old_l2_entry = be64_to_cpu(l2_table[l2_index + i]);
|
old_l2_entry = be64_to_cpu(l2_slice[l2_index + i]);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If full_discard is false, make sure that a discarded area reads back
|
* If full_discard is false, make sure that a discarded area reads back
|
||||||
|
@ -1679,18 +1694,18 @@ static int discard_single_l2(BlockDriverState *bs, uint64_t offset,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* First remove L2 entries */
|
/* First remove L2 entries */
|
||||||
qcow2_cache_entry_mark_dirty(bs, s->l2_table_cache, l2_table);
|
qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_slice);
|
||||||
if (!full_discard && s->qcow_version >= 3) {
|
if (!full_discard && s->qcow_version >= 3) {
|
||||||
l2_table[l2_index + i] = cpu_to_be64(QCOW_OFLAG_ZERO);
|
l2_slice[l2_index + i] = cpu_to_be64(QCOW_OFLAG_ZERO);
|
||||||
} else {
|
} else {
|
||||||
l2_table[l2_index + i] = cpu_to_be64(0);
|
l2_slice[l2_index + i] = cpu_to_be64(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Then decrease the refcount */
|
/* Then decrease the refcount */
|
||||||
qcow2_free_any_clusters(bs, old_l2_entry, 1, type);
|
qcow2_free_any_clusters(bs, old_l2_entry, 1, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void **) &l2_table);
|
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||||
|
|
||||||
return nb_clusters;
|
return nb_clusters;
|
||||||
}
|
}
|
||||||
|
@ -1714,9 +1729,9 @@ int qcow2_cluster_discard(BlockDriverState *bs, uint64_t offset,
|
||||||
|
|
||||||
s->cache_discards = true;
|
s->cache_discards = true;
|
||||||
|
|
||||||
/* Each L2 table is handled by its own loop iteration */
|
/* Each L2 slice is handled by its own loop iteration */
|
||||||
while (nb_clusters > 0) {
|
while (nb_clusters > 0) {
|
||||||
cleared = discard_single_l2(bs, offset, nb_clusters, type,
|
cleared = discard_in_l2_slice(bs, offset, nb_clusters, type,
|
||||||
full_discard);
|
full_discard);
|
||||||
if (cleared < 0) {
|
if (cleared < 0) {
|
||||||
ret = cleared;
|
ret = cleared;
|
||||||
|
@ -1737,33 +1752,33 @@ fail:
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This zeroes as many clusters of nb_clusters as possible at once (i.e.
|
* This zeroes as many clusters of nb_clusters as possible at once (i.e.
|
||||||
* all clusters in the same L2 table) and returns the number of zeroed
|
* all clusters in the same L2 slice) and returns the number of zeroed
|
||||||
* clusters.
|
* clusters.
|
||||||
*/
|
*/
|
||||||
static int zero_single_l2(BlockDriverState *bs, uint64_t offset,
|
static int zero_in_l2_slice(BlockDriverState *bs, uint64_t offset,
|
||||||
uint64_t nb_clusters, int flags)
|
uint64_t nb_clusters, int flags)
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
uint64_t *l2_table;
|
uint64_t *l2_slice;
|
||||||
int l2_index;
|
int l2_index;
|
||||||
int ret;
|
int ret;
|
||||||
int i;
|
int i;
|
||||||
bool unmap = !!(flags & BDRV_REQ_MAY_UNMAP);
|
bool unmap = !!(flags & BDRV_REQ_MAY_UNMAP);
|
||||||
|
|
||||||
ret = get_cluster_table(bs, offset, &l2_table, &l2_index);
|
ret = get_cluster_table(bs, offset, &l2_slice, &l2_index);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Limit nb_clusters to one L2 table */
|
/* Limit nb_clusters to one L2 slice */
|
||||||
nb_clusters = MIN(nb_clusters, s->l2_size - l2_index);
|
nb_clusters = MIN(nb_clusters, s->l2_slice_size - l2_index);
|
||||||
assert(nb_clusters <= INT_MAX);
|
assert(nb_clusters <= INT_MAX);
|
||||||
|
|
||||||
for (i = 0; i < nb_clusters; i++) {
|
for (i = 0; i < nb_clusters; i++) {
|
||||||
uint64_t old_offset;
|
uint64_t old_offset;
|
||||||
QCow2ClusterType cluster_type;
|
QCow2ClusterType cluster_type;
|
||||||
|
|
||||||
old_offset = be64_to_cpu(l2_table[l2_index + i]);
|
old_offset = be64_to_cpu(l2_slice[l2_index + i]);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Minimize L2 changes if the cluster already reads back as
|
* Minimize L2 changes if the cluster already reads back as
|
||||||
|
@ -1775,16 +1790,16 @@ static int zero_single_l2(BlockDriverState *bs, uint64_t offset,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
qcow2_cache_entry_mark_dirty(bs, s->l2_table_cache, l2_table);
|
qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_slice);
|
||||||
if (cluster_type == QCOW2_CLUSTER_COMPRESSED || unmap) {
|
if (cluster_type == QCOW2_CLUSTER_COMPRESSED || unmap) {
|
||||||
l2_table[l2_index + i] = cpu_to_be64(QCOW_OFLAG_ZERO);
|
l2_slice[l2_index + i] = cpu_to_be64(QCOW_OFLAG_ZERO);
|
||||||
qcow2_free_any_clusters(bs, old_offset, 1, QCOW2_DISCARD_REQUEST);
|
qcow2_free_any_clusters(bs, old_offset, 1, QCOW2_DISCARD_REQUEST);
|
||||||
} else {
|
} else {
|
||||||
l2_table[l2_index + i] |= cpu_to_be64(QCOW_OFLAG_ZERO);
|
l2_slice[l2_index + i] |= cpu_to_be64(QCOW_OFLAG_ZERO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void **) &l2_table);
|
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||||
|
|
||||||
return nb_clusters;
|
return nb_clusters;
|
||||||
}
|
}
|
||||||
|
@ -1808,13 +1823,13 @@ int qcow2_cluster_zeroize(BlockDriverState *bs, uint64_t offset,
|
||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Each L2 table is handled by its own loop iteration */
|
/* Each L2 slice is handled by its own loop iteration */
|
||||||
nb_clusters = size_to_clusters(s, bytes);
|
nb_clusters = size_to_clusters(s, bytes);
|
||||||
|
|
||||||
s->cache_discards = true;
|
s->cache_discards = true;
|
||||||
|
|
||||||
while (nb_clusters > 0) {
|
while (nb_clusters > 0) {
|
||||||
cleared = zero_single_l2(bs, offset, nb_clusters, flags);
|
cleared = zero_in_l2_slice(bs, offset, nb_clusters, flags);
|
||||||
if (cleared < 0) {
|
if (cleared < 0) {
|
||||||
ret = cleared;
|
ret = cleared;
|
||||||
goto fail;
|
goto fail;
|
||||||
|
@ -1848,22 +1863,25 @@ static int expand_zero_clusters_in_l1(BlockDriverState *bs, uint64_t *l1_table,
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
bool is_active_l1 = (l1_table == s->l1_table);
|
bool is_active_l1 = (l1_table == s->l1_table);
|
||||||
uint64_t *l2_table = NULL;
|
uint64_t *l2_slice = NULL;
|
||||||
|
unsigned slice, slice_size2, n_slices;
|
||||||
int ret;
|
int ret;
|
||||||
int i, j;
|
int i, j;
|
||||||
|
|
||||||
|
slice_size2 = s->l2_slice_size * sizeof(uint64_t);
|
||||||
|
n_slices = s->cluster_size / slice_size2;
|
||||||
|
|
||||||
if (!is_active_l1) {
|
if (!is_active_l1) {
|
||||||
/* inactive L2 tables require a buffer to be stored in when loading
|
/* inactive L2 tables require a buffer to be stored in when loading
|
||||||
* them from disk */
|
* them from disk */
|
||||||
l2_table = qemu_try_blockalign(bs->file->bs, s->cluster_size);
|
l2_slice = qemu_try_blockalign(bs->file->bs, slice_size2);
|
||||||
if (l2_table == NULL) {
|
if (l2_slice == NULL) {
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < l1_size; i++) {
|
for (i = 0; i < l1_size; i++) {
|
||||||
uint64_t l2_offset = l1_table[i] & L1E_OFFSET_MASK;
|
uint64_t l2_offset = l1_table[i] & L1E_OFFSET_MASK;
|
||||||
bool l2_dirty = false;
|
|
||||||
uint64_t l2_refcount;
|
uint64_t l2_refcount;
|
||||||
|
|
||||||
if (!l2_offset) {
|
if (!l2_offset) {
|
||||||
|
@ -1883,29 +1901,32 @@ static int expand_zero_clusters_in_l1(BlockDriverState *bs, uint64_t *l1_table,
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_active_l1) {
|
|
||||||
/* get active L2 tables from cache */
|
|
||||||
ret = qcow2_cache_get(bs, s->l2_table_cache, l2_offset,
|
|
||||||
(void **)&l2_table);
|
|
||||||
} else {
|
|
||||||
/* load inactive L2 tables from disk */
|
|
||||||
ret = bdrv_read(bs->file, l2_offset / BDRV_SECTOR_SIZE,
|
|
||||||
(void *)l2_table, s->cluster_sectors);
|
|
||||||
}
|
|
||||||
if (ret < 0) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = qcow2_get_refcount(bs, l2_offset >> s->cluster_bits,
|
ret = qcow2_get_refcount(bs, l2_offset >> s->cluster_bits,
|
||||||
&l2_refcount);
|
&l2_refcount);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (j = 0; j < s->l2_size; j++) {
|
for (slice = 0; slice < n_slices; slice++) {
|
||||||
uint64_t l2_entry = be64_to_cpu(l2_table[j]);
|
uint64_t slice_offset = l2_offset + slice * slice_size2;
|
||||||
|
bool l2_dirty = false;
|
||||||
|
if (is_active_l1) {
|
||||||
|
/* get active L2 tables from cache */
|
||||||
|
ret = qcow2_cache_get(bs, s->l2_table_cache, slice_offset,
|
||||||
|
(void **)&l2_slice);
|
||||||
|
} else {
|
||||||
|
/* load inactive L2 tables from disk */
|
||||||
|
ret = bdrv_pread(bs->file, slice_offset, l2_slice, slice_size2);
|
||||||
|
}
|
||||||
|
if (ret < 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (j = 0; j < s->l2_slice_size; j++) {
|
||||||
|
uint64_t l2_entry = be64_to_cpu(l2_slice[j]);
|
||||||
int64_t offset = l2_entry & L2E_OFFSET_MASK;
|
int64_t offset = l2_entry & L2E_OFFSET_MASK;
|
||||||
QCow2ClusterType cluster_type = qcow2_get_cluster_type(l2_entry);
|
QCow2ClusterType cluster_type =
|
||||||
|
qcow2_get_cluster_type(l2_entry);
|
||||||
|
|
||||||
if (cluster_type != QCOW2_CLUSTER_ZERO_PLAIN &&
|
if (cluster_type != QCOW2_CLUSTER_ZERO_PLAIN &&
|
||||||
cluster_type != QCOW2_CLUSTER_ZERO_ALLOC) {
|
cluster_type != QCOW2_CLUSTER_ZERO_ALLOC) {
|
||||||
|
@ -1916,7 +1937,7 @@ static int expand_zero_clusters_in_l1(BlockDriverState *bs, uint64_t *l1_table,
|
||||||
if (!bs->backing) {
|
if (!bs->backing) {
|
||||||
/* not backed; therefore we can simply deallocate the
|
/* not backed; therefore we can simply deallocate the
|
||||||
* cluster */
|
* cluster */
|
||||||
l2_table[j] = 0;
|
l2_slice[j] = 0;
|
||||||
l2_dirty = true;
|
l2_dirty = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1928,10 +1949,10 @@ static int expand_zero_clusters_in_l1(BlockDriverState *bs, uint64_t *l1_table,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (l2_refcount > 1) {
|
if (l2_refcount > 1) {
|
||||||
/* For shared L2 tables, set the refcount accordingly (it is
|
/* For shared L2 tables, set the refcount accordingly
|
||||||
* already 1 and needs to be l2_refcount) */
|
* (it is already 1 and needs to be l2_refcount) */
|
||||||
ret = qcow2_update_cluster_refcount(bs,
|
ret = qcow2_update_cluster_refcount(
|
||||||
offset >> s->cluster_bits,
|
bs, offset >> s->cluster_bits,
|
||||||
refcount_diff(1, l2_refcount), false,
|
refcount_diff(1, l2_refcount), false,
|
||||||
QCOW2_DISCARD_OTHER);
|
QCOW2_DISCARD_OTHER);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
@ -1943,11 +1964,13 @@ static int expand_zero_clusters_in_l1(BlockDriverState *bs, uint64_t *l1_table,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (offset_into_cluster(s, offset)) {
|
if (offset_into_cluster(s, offset)) {
|
||||||
qcow2_signal_corruption(bs, true, -1, -1,
|
int l2_index = slice * s->l2_slice_size + j;
|
||||||
|
qcow2_signal_corruption(
|
||||||
|
bs, true, -1, -1,
|
||||||
"Cluster allocation offset "
|
"Cluster allocation offset "
|
||||||
"%#" PRIx64 " unaligned (L2 offset: %#"
|
"%#" PRIx64 " unaligned (L2 offset: %#"
|
||||||
PRIx64 ", L2 index: %#x)", offset,
|
PRIx64 ", L2 index: %#x)", offset,
|
||||||
l2_offset, j);
|
l2_offset, l2_index);
|
||||||
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN) {
|
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN) {
|
||||||
qcow2_free_clusters(bs, offset, s->cluster_size,
|
qcow2_free_clusters(bs, offset, s->cluster_size,
|
||||||
QCOW2_DISCARD_ALWAYS);
|
QCOW2_DISCARD_ALWAYS);
|
||||||
|
@ -1956,7 +1979,8 @@ static int expand_zero_clusters_in_l1(BlockDriverState *bs, uint64_t *l1_table,
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = qcow2_pre_write_overlap_check(bs, 0, offset, s->cluster_size);
|
ret = qcow2_pre_write_overlap_check(bs, 0, offset,
|
||||||
|
s->cluster_size);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN) {
|
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN) {
|
||||||
qcow2_free_clusters(bs, offset, s->cluster_size,
|
qcow2_free_clusters(bs, offset, s->cluster_size,
|
||||||
|
@ -1975,35 +1999,36 @@ static int expand_zero_clusters_in_l1(BlockDriverState *bs, uint64_t *l1_table,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (l2_refcount == 1) {
|
if (l2_refcount == 1) {
|
||||||
l2_table[j] = cpu_to_be64(offset | QCOW_OFLAG_COPIED);
|
l2_slice[j] = cpu_to_be64(offset | QCOW_OFLAG_COPIED);
|
||||||
} else {
|
} else {
|
||||||
l2_table[j] = cpu_to_be64(offset);
|
l2_slice[j] = cpu_to_be64(offset);
|
||||||
}
|
}
|
||||||
l2_dirty = true;
|
l2_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_active_l1) {
|
if (is_active_l1) {
|
||||||
if (l2_dirty) {
|
if (l2_dirty) {
|
||||||
qcow2_cache_entry_mark_dirty(bs, s->l2_table_cache, l2_table);
|
qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_slice);
|
||||||
qcow2_cache_depends_on_flush(s->l2_table_cache);
|
qcow2_cache_depends_on_flush(s->l2_table_cache);
|
||||||
}
|
}
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void **) &l2_table);
|
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||||
} else {
|
} else {
|
||||||
if (l2_dirty) {
|
if (l2_dirty) {
|
||||||
ret = qcow2_pre_write_overlap_check(bs,
|
ret = qcow2_pre_write_overlap_check(
|
||||||
QCOW2_OL_INACTIVE_L2 | QCOW2_OL_ACTIVE_L2, l2_offset,
|
bs, QCOW2_OL_INACTIVE_L2 | QCOW2_OL_ACTIVE_L2,
|
||||||
s->cluster_size);
|
slice_offset, slice_size2);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = bdrv_write(bs->file, l2_offset / BDRV_SECTOR_SIZE,
|
ret = bdrv_pwrite(bs->file, slice_offset,
|
||||||
(void *)l2_table, s->cluster_sectors);
|
l2_slice, slice_size2);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(*visited_l1_entries)++;
|
(*visited_l1_entries)++;
|
||||||
if (status_cb) {
|
if (status_cb) {
|
||||||
|
@ -2014,11 +2039,11 @@ static int expand_zero_clusters_in_l1(BlockDriverState *bs, uint64_t *l1_table,
|
||||||
ret = 0;
|
ret = 0;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
if (l2_table) {
|
if (l2_slice) {
|
||||||
if (!is_active_l1) {
|
if (!is_active_l1) {
|
||||||
qemu_vfree(l2_table);
|
qemu_vfree(l2_slice);
|
||||||
} else {
|
} else {
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void **) &l2_table);
|
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -277,7 +277,7 @@ int qcow2_get_refcount(BlockDriverState *bs, int64_t cluster_index,
|
||||||
block_index = cluster_index & (s->refcount_block_size - 1);
|
block_index = cluster_index & (s->refcount_block_size - 1);
|
||||||
*refcount = s->get_refcount(refcount_block, block_index);
|
*refcount = s->get_refcount(refcount_block, block_index);
|
||||||
|
|
||||||
qcow2_cache_put(bs, s->refcount_block_cache, &refcount_block);
|
qcow2_cache_put(s->refcount_block_cache, &refcount_block);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -421,7 +421,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||||
|
|
||||||
/* Now the new refcount block needs to be written to disk */
|
/* Now the new refcount block needs to be written to disk */
|
||||||
BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_ALLOC_WRITE);
|
BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_ALLOC_WRITE);
|
||||||
qcow2_cache_entry_mark_dirty(bs, s->refcount_block_cache, *refcount_block);
|
qcow2_cache_entry_mark_dirty(s->refcount_block_cache, *refcount_block);
|
||||||
ret = qcow2_cache_flush(bs, s->refcount_block_cache);
|
ret = qcow2_cache_flush(bs, s->refcount_block_cache);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
|
@ -449,7 +449,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||||
return -EAGAIN;
|
return -EAGAIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
qcow2_cache_put(bs, s->refcount_block_cache, refcount_block);
|
qcow2_cache_put(s->refcount_block_cache, refcount_block);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If we come here, we need to grow the refcount table. Again, a new
|
* If we come here, we need to grow the refcount table. Again, a new
|
||||||
|
@ -501,7 +501,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
if (*refcount_block != NULL) {
|
if (*refcount_block != NULL) {
|
||||||
qcow2_cache_put(bs, s->refcount_block_cache, refcount_block);
|
qcow2_cache_put(s->refcount_block_cache, refcount_block);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -623,7 +623,7 @@ int64_t qcow2_refcount_area(BlockDriverState *bs, uint64_t start_offset,
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
memset(refblock_data, 0, s->cluster_size);
|
memset(refblock_data, 0, s->cluster_size);
|
||||||
qcow2_cache_entry_mark_dirty(bs, s->refcount_block_cache,
|
qcow2_cache_entry_mark_dirty(s->refcount_block_cache,
|
||||||
refblock_data);
|
refblock_data);
|
||||||
|
|
||||||
new_table[i] = block_offset;
|
new_table[i] = block_offset;
|
||||||
|
@ -656,11 +656,11 @@ int64_t qcow2_refcount_area(BlockDriverState *bs, uint64_t start_offset,
|
||||||
s->set_refcount(refblock_data, j, 1);
|
s->set_refcount(refblock_data, j, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
qcow2_cache_entry_mark_dirty(bs, s->refcount_block_cache,
|
qcow2_cache_entry_mark_dirty(s->refcount_block_cache,
|
||||||
refblock_data);
|
refblock_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
qcow2_cache_put(bs, s->refcount_block_cache, &refblock_data);
|
qcow2_cache_put(s->refcount_block_cache, &refblock_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(block_offset == table_offset);
|
assert(block_offset == table_offset);
|
||||||
|
@ -836,7 +836,7 @@ static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
|
||||||
/* Load the refcount block and allocate it if needed */
|
/* Load the refcount block and allocate it if needed */
|
||||||
if (table_index != old_table_index) {
|
if (table_index != old_table_index) {
|
||||||
if (refcount_block) {
|
if (refcount_block) {
|
||||||
qcow2_cache_put(bs, s->refcount_block_cache, &refcount_block);
|
qcow2_cache_put(s->refcount_block_cache, &refcount_block);
|
||||||
}
|
}
|
||||||
ret = alloc_refcount_block(bs, cluster_index, &refcount_block);
|
ret = alloc_refcount_block(bs, cluster_index, &refcount_block);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
@ -845,8 +845,7 @@ static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
|
||||||
}
|
}
|
||||||
old_table_index = table_index;
|
old_table_index = table_index;
|
||||||
|
|
||||||
qcow2_cache_entry_mark_dirty(bs, s->refcount_block_cache,
|
qcow2_cache_entry_mark_dirty(s->refcount_block_cache, refcount_block);
|
||||||
refcount_block);
|
|
||||||
|
|
||||||
/* we can update the count and save it */
|
/* we can update the count and save it */
|
||||||
block_index = cluster_index & (s->refcount_block_size - 1);
|
block_index = cluster_index & (s->refcount_block_size - 1);
|
||||||
|
@ -872,16 +871,16 @@ static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
|
||||||
if (refcount == 0) {
|
if (refcount == 0) {
|
||||||
void *table;
|
void *table;
|
||||||
|
|
||||||
table = qcow2_cache_is_table_offset(bs, s->refcount_block_cache,
|
table = qcow2_cache_is_table_offset(s->refcount_block_cache,
|
||||||
offset);
|
offset);
|
||||||
if (table != NULL) {
|
if (table != NULL) {
|
||||||
qcow2_cache_put(bs, s->refcount_block_cache, &refcount_block);
|
qcow2_cache_put(s->refcount_block_cache, &refcount_block);
|
||||||
qcow2_cache_discard(bs, s->refcount_block_cache, table);
|
qcow2_cache_discard(s->refcount_block_cache, table);
|
||||||
}
|
}
|
||||||
|
|
||||||
table = qcow2_cache_is_table_offset(bs, s->l2_table_cache, offset);
|
table = qcow2_cache_is_table_offset(s->l2_table_cache, offset);
|
||||||
if (table != NULL) {
|
if (table != NULL) {
|
||||||
qcow2_cache_discard(bs, s->l2_table_cache, table);
|
qcow2_cache_discard(s->l2_table_cache, table);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s->discard_passthrough[type]) {
|
if (s->discard_passthrough[type]) {
|
||||||
|
@ -898,7 +897,7 @@ fail:
|
||||||
|
|
||||||
/* Write last changed block to disk */
|
/* Write last changed block to disk */
|
||||||
if (refcount_block) {
|
if (refcount_block) {
|
||||||
qcow2_cache_put(bs, s->refcount_block_cache, &refcount_block);
|
qcow2_cache_put(s->refcount_block_cache, &refcount_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1184,17 +1183,20 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||||
int64_t l1_table_offset, int l1_size, int addend)
|
int64_t l1_table_offset, int l1_size, int addend)
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
uint64_t *l1_table, *l2_table, l2_offset, entry, l1_size2, refcount;
|
uint64_t *l1_table, *l2_slice, l2_offset, entry, l1_size2, refcount;
|
||||||
bool l1_allocated = false;
|
bool l1_allocated = false;
|
||||||
int64_t old_entry, old_l2_offset;
|
int64_t old_entry, old_l2_offset;
|
||||||
|
unsigned slice, slice_size2, n_slices;
|
||||||
int i, j, l1_modified = 0, nb_csectors;
|
int i, j, l1_modified = 0, nb_csectors;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
assert(addend >= -1 && addend <= 1);
|
assert(addend >= -1 && addend <= 1);
|
||||||
|
|
||||||
l2_table = NULL;
|
l2_slice = NULL;
|
||||||
l1_table = NULL;
|
l1_table = NULL;
|
||||||
l1_size2 = l1_size * sizeof(uint64_t);
|
l1_size2 = l1_size * sizeof(uint64_t);
|
||||||
|
slice_size2 = s->l2_slice_size * sizeof(uint64_t);
|
||||||
|
n_slices = s->cluster_size / slice_size2;
|
||||||
|
|
||||||
s->cache_discards = true;
|
s->cache_discards = true;
|
||||||
|
|
||||||
|
@ -1237,17 +1239,19 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = qcow2_cache_get(bs, s->l2_table_cache, l2_offset,
|
for (slice = 0; slice < n_slices; slice++) {
|
||||||
(void**) &l2_table);
|
ret = qcow2_cache_get(bs, s->l2_table_cache,
|
||||||
|
l2_offset + slice * slice_size2,
|
||||||
|
(void **) &l2_slice);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (j = 0; j < s->l2_size; j++) {
|
for (j = 0; j < s->l2_slice_size; j++) {
|
||||||
uint64_t cluster_index;
|
uint64_t cluster_index;
|
||||||
uint64_t offset;
|
uint64_t offset;
|
||||||
|
|
||||||
entry = be64_to_cpu(l2_table[j]);
|
entry = be64_to_cpu(l2_slice[j]);
|
||||||
old_entry = entry;
|
old_entry = entry;
|
||||||
entry &= ~QCOW_OFLAG_COPIED;
|
entry &= ~QCOW_OFLAG_COPIED;
|
||||||
offset = entry & L2E_OFFSET_MASK;
|
offset = entry & L2E_OFFSET_MASK;
|
||||||
|
@ -1257,8 +1261,8 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||||
nb_csectors = ((entry >> s->csize_shift) &
|
nb_csectors = ((entry >> s->csize_shift) &
|
||||||
s->csize_mask) + 1;
|
s->csize_mask) + 1;
|
||||||
if (addend != 0) {
|
if (addend != 0) {
|
||||||
ret = update_refcount(bs,
|
ret = update_refcount(
|
||||||
(entry & s->cluster_offset_mask) & ~511,
|
bs, (entry & s->cluster_offset_mask) & ~511,
|
||||||
nb_csectors * 512, abs(addend), addend < 0,
|
nb_csectors * 512, abs(addend), addend < 0,
|
||||||
QCOW2_DISCARD_SNAPSHOT);
|
QCOW2_DISCARD_SNAPSHOT);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
@ -1272,11 +1276,14 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||||
case QCOW2_CLUSTER_NORMAL:
|
case QCOW2_CLUSTER_NORMAL:
|
||||||
case QCOW2_CLUSTER_ZERO_ALLOC:
|
case QCOW2_CLUSTER_ZERO_ALLOC:
|
||||||
if (offset_into_cluster(s, offset)) {
|
if (offset_into_cluster(s, offset)) {
|
||||||
qcow2_signal_corruption(bs, true, -1, -1, "Cluster "
|
/* Here l2_index means table (not slice) index */
|
||||||
|
int l2_index = slice * s->l2_slice_size + j;
|
||||||
|
qcow2_signal_corruption(
|
||||||
|
bs, true, -1, -1, "Cluster "
|
||||||
"allocation offset %#" PRIx64
|
"allocation offset %#" PRIx64
|
||||||
" unaligned (L2 offset: %#"
|
" unaligned (L2 offset: %#"
|
||||||
PRIx64 ", L2 index: %#x)",
|
PRIx64 ", L2 index: %#x)",
|
||||||
offset, l2_offset, j);
|
offset, l2_offset, l2_index);
|
||||||
ret = -EIO;
|
ret = -EIO;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
@ -1284,8 +1291,8 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||||
cluster_index = offset >> s->cluster_bits;
|
cluster_index = offset >> s->cluster_bits;
|
||||||
assert(cluster_index);
|
assert(cluster_index);
|
||||||
if (addend != 0) {
|
if (addend != 0) {
|
||||||
ret = qcow2_update_cluster_refcount(bs,
|
ret = qcow2_update_cluster_refcount(
|
||||||
cluster_index, abs(addend), addend < 0,
|
bs, cluster_index, abs(addend), addend < 0,
|
||||||
QCOW2_DISCARD_SNAPSHOT);
|
QCOW2_DISCARD_SNAPSHOT);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
|
@ -1315,13 +1322,14 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||||
qcow2_cache_set_dependency(bs, s->l2_table_cache,
|
qcow2_cache_set_dependency(bs, s->l2_table_cache,
|
||||||
s->refcount_block_cache);
|
s->refcount_block_cache);
|
||||||
}
|
}
|
||||||
l2_table[j] = cpu_to_be64(entry);
|
l2_slice[j] = cpu_to_be64(entry);
|
||||||
qcow2_cache_entry_mark_dirty(bs, s->l2_table_cache,
|
qcow2_cache_entry_mark_dirty(s->l2_table_cache,
|
||||||
l2_table);
|
l2_slice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void **) &l2_table);
|
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||||
|
}
|
||||||
|
|
||||||
if (addend != 0) {
|
if (addend != 0) {
|
||||||
ret = qcow2_update_cluster_refcount(bs, l2_offset >>
|
ret = qcow2_update_cluster_refcount(bs, l2_offset >>
|
||||||
|
@ -1348,8 +1356,8 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
||||||
|
|
||||||
ret = bdrv_flush(bs);
|
ret = bdrv_flush(bs);
|
||||||
fail:
|
fail:
|
||||||
if (l2_table) {
|
if (l2_slice) {
|
||||||
qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table);
|
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||||
}
|
}
|
||||||
|
|
||||||
s->cache_discards = false;
|
s->cache_discards = false;
|
||||||
|
@ -2849,7 +2857,7 @@ static int walk_over_reftable(BlockDriverState *bs, uint64_t **new_reftable,
|
||||||
new_reftable_size, new_refblock,
|
new_reftable_size, new_refblock,
|
||||||
new_refblock_empty, allocated, errp);
|
new_refblock_empty, allocated, errp);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
qcow2_cache_put(bs, s->refcount_block_cache, &refblock);
|
qcow2_cache_put(s->refcount_block_cache, &refblock);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2862,7 +2870,7 @@ static int walk_over_reftable(BlockDriverState *bs, uint64_t **new_reftable,
|
||||||
if (new_refcount_bits < 64 && refcount >> new_refcount_bits) {
|
if (new_refcount_bits < 64 && refcount >> new_refcount_bits) {
|
||||||
uint64_t offset;
|
uint64_t offset;
|
||||||
|
|
||||||
qcow2_cache_put(bs, s->refcount_block_cache, &refblock);
|
qcow2_cache_put(s->refcount_block_cache, &refblock);
|
||||||
|
|
||||||
offset = ((reftable_index << s->refcount_block_bits)
|
offset = ((reftable_index << s->refcount_block_bits)
|
||||||
+ refblock_index) << s->cluster_bits;
|
+ refblock_index) << s->cluster_bits;
|
||||||
|
@ -2883,7 +2891,7 @@ static int walk_over_reftable(BlockDriverState *bs, uint64_t **new_reftable,
|
||||||
new_refblock_empty = new_refblock_empty && refcount == 0;
|
new_refblock_empty = new_refblock_empty && refcount == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
qcow2_cache_put(bs, s->refcount_block_cache, &refblock);
|
qcow2_cache_put(s->refcount_block_cache, &refblock);
|
||||||
} else {
|
} else {
|
||||||
/* No refblock means every refcount is 0 */
|
/* No refblock means every refcount is 0 */
|
||||||
for (refblock_index = 0; refblock_index < s->refcount_block_size;
|
for (refblock_index = 0; refblock_index < s->refcount_block_size;
|
||||||
|
@ -3175,24 +3183,24 @@ static int qcow2_discard_refcount_block(BlockDriverState *bs,
|
||||||
offset_to_reftable_index(s, discard_block_offs),
|
offset_to_reftable_index(s, discard_block_offs),
|
||||||
discard_block_offs,
|
discard_block_offs,
|
||||||
s->get_refcount(refblock, block_index));
|
s->get_refcount(refblock, block_index));
|
||||||
qcow2_cache_put(bs, s->refcount_block_cache, &refblock);
|
qcow2_cache_put(s->refcount_block_cache, &refblock);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
s->set_refcount(refblock, block_index, 0);
|
s->set_refcount(refblock, block_index, 0);
|
||||||
|
|
||||||
qcow2_cache_entry_mark_dirty(bs, s->refcount_block_cache, refblock);
|
qcow2_cache_entry_mark_dirty(s->refcount_block_cache, refblock);
|
||||||
|
|
||||||
qcow2_cache_put(bs, s->refcount_block_cache, &refblock);
|
qcow2_cache_put(s->refcount_block_cache, &refblock);
|
||||||
|
|
||||||
if (cluster_index < s->free_cluster_index) {
|
if (cluster_index < s->free_cluster_index) {
|
||||||
s->free_cluster_index = cluster_index;
|
s->free_cluster_index = cluster_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
refblock = qcow2_cache_is_table_offset(bs, s->refcount_block_cache,
|
refblock = qcow2_cache_is_table_offset(s->refcount_block_cache,
|
||||||
discard_block_offs);
|
discard_block_offs);
|
||||||
if (refblock) {
|
if (refblock) {
|
||||||
/* discard refblock from the cache if refblock is cached */
|
/* discard refblock from the cache if refblock is cached */
|
||||||
qcow2_cache_discard(bs, s->refcount_block_cache, refblock);
|
qcow2_cache_discard(s->refcount_block_cache, refblock);
|
||||||
}
|
}
|
||||||
update_refcount_discard(bs, discard_block_offs, s->cluster_size);
|
update_refcount_discard(bs, discard_block_offs, s->cluster_size);
|
||||||
|
|
||||||
|
@ -3235,7 +3243,7 @@ int qcow2_shrink_reftable(BlockDriverState *bs)
|
||||||
} else {
|
} else {
|
||||||
unused_block = buffer_is_zero(refblock, s->cluster_size);
|
unused_block = buffer_is_zero(refblock, s->cluster_size);
|
||||||
}
|
}
|
||||||
qcow2_cache_put(bs, s->refcount_block_cache, &refblock);
|
qcow2_cache_put(s->refcount_block_cache, &refblock);
|
||||||
|
|
||||||
reftable_tmp[i] = unused_block ? 0 : cpu_to_be64(s->refcount_table[i]);
|
reftable_tmp[i] = unused_block ? 0 : cpu_to_be64(s->refcount_table[i]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -675,6 +675,11 @@ static QemuOptsList qcow2_runtime_opts = {
|
||||||
.type = QEMU_OPT_SIZE,
|
.type = QEMU_OPT_SIZE,
|
||||||
.help = "Maximum L2 table cache size",
|
.help = "Maximum L2 table cache size",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.name = QCOW2_OPT_L2_CACHE_ENTRY_SIZE,
|
||||||
|
.type = QEMU_OPT_SIZE,
|
||||||
|
.help = "Size of each entry in the L2 cache",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.name = QCOW2_OPT_REFCOUNT_CACHE_SIZE,
|
.name = QCOW2_OPT_REFCOUNT_CACHE_SIZE,
|
||||||
.type = QEMU_OPT_SIZE,
|
.type = QEMU_OPT_SIZE,
|
||||||
|
@ -706,8 +711,8 @@ static void cache_clean_timer_cb(void *opaque)
|
||||||
{
|
{
|
||||||
BlockDriverState *bs = opaque;
|
BlockDriverState *bs = opaque;
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
qcow2_cache_clean_unused(bs, s->l2_table_cache);
|
qcow2_cache_clean_unused(s->l2_table_cache);
|
||||||
qcow2_cache_clean_unused(bs, s->refcount_block_cache);
|
qcow2_cache_clean_unused(s->refcount_block_cache);
|
||||||
timer_mod(s->cache_clean_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
|
timer_mod(s->cache_clean_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
|
||||||
(int64_t) s->cache_clean_interval * 1000);
|
(int64_t) s->cache_clean_interval * 1000);
|
||||||
}
|
}
|
||||||
|
@ -747,6 +752,7 @@ static void qcow2_attach_aio_context(BlockDriverState *bs,
|
||||||
|
|
||||||
static void read_cache_sizes(BlockDriverState *bs, QemuOpts *opts,
|
static void read_cache_sizes(BlockDriverState *bs, QemuOpts *opts,
|
||||||
uint64_t *l2_cache_size,
|
uint64_t *l2_cache_size,
|
||||||
|
uint64_t *l2_cache_entry_size,
|
||||||
uint64_t *refcount_cache_size, Error **errp)
|
uint64_t *refcount_cache_size, Error **errp)
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
|
@ -762,6 +768,9 @@ static void read_cache_sizes(BlockDriverState *bs, QemuOpts *opts,
|
||||||
*refcount_cache_size = qemu_opt_get_size(opts,
|
*refcount_cache_size = qemu_opt_get_size(opts,
|
||||||
QCOW2_OPT_REFCOUNT_CACHE_SIZE, 0);
|
QCOW2_OPT_REFCOUNT_CACHE_SIZE, 0);
|
||||||
|
|
||||||
|
*l2_cache_entry_size = qemu_opt_get_size(
|
||||||
|
opts, QCOW2_OPT_L2_CACHE_ENTRY_SIZE, s->cluster_size);
|
||||||
|
|
||||||
if (combined_cache_size_set) {
|
if (combined_cache_size_set) {
|
||||||
if (l2_cache_size_set && refcount_cache_size_set) {
|
if (l2_cache_size_set && refcount_cache_size_set) {
|
||||||
error_setg(errp, QCOW2_OPT_CACHE_SIZE ", " QCOW2_OPT_L2_CACHE_SIZE
|
error_setg(errp, QCOW2_OPT_CACHE_SIZE ", " QCOW2_OPT_L2_CACHE_SIZE
|
||||||
|
@ -802,11 +811,21 @@ static void read_cache_sizes(BlockDriverState *bs, QemuOpts *opts,
|
||||||
/ DEFAULT_L2_REFCOUNT_SIZE_RATIO;
|
/ DEFAULT_L2_REFCOUNT_SIZE_RATIO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (*l2_cache_entry_size < (1 << MIN_CLUSTER_BITS) ||
|
||||||
|
*l2_cache_entry_size > s->cluster_size ||
|
||||||
|
!is_power_of_2(*l2_cache_entry_size)) {
|
||||||
|
error_setg(errp, "L2 cache entry size must be a power of two "
|
||||||
|
"between %d and the cluster size (%d)",
|
||||||
|
1 << MIN_CLUSTER_BITS, s->cluster_size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct Qcow2ReopenState {
|
typedef struct Qcow2ReopenState {
|
||||||
Qcow2Cache *l2_table_cache;
|
Qcow2Cache *l2_table_cache;
|
||||||
Qcow2Cache *refcount_block_cache;
|
Qcow2Cache *refcount_block_cache;
|
||||||
|
int l2_slice_size; /* Number of entries in a slice of the L2 table */
|
||||||
bool use_lazy_refcounts;
|
bool use_lazy_refcounts;
|
||||||
int overlap_check;
|
int overlap_check;
|
||||||
bool discard_passthrough[QCOW2_DISCARD_MAX];
|
bool discard_passthrough[QCOW2_DISCARD_MAX];
|
||||||
|
@ -823,7 +842,7 @@ static int qcow2_update_options_prepare(BlockDriverState *bs,
|
||||||
QemuOpts *opts = NULL;
|
QemuOpts *opts = NULL;
|
||||||
const char *opt_overlap_check, *opt_overlap_check_template;
|
const char *opt_overlap_check, *opt_overlap_check_template;
|
||||||
int overlap_check_template = 0;
|
int overlap_check_template = 0;
|
||||||
uint64_t l2_cache_size, refcount_cache_size;
|
uint64_t l2_cache_size, l2_cache_entry_size, refcount_cache_size;
|
||||||
int i;
|
int i;
|
||||||
const char *encryptfmt;
|
const char *encryptfmt;
|
||||||
QDict *encryptopts = NULL;
|
QDict *encryptopts = NULL;
|
||||||
|
@ -842,15 +861,15 @@ static int qcow2_update_options_prepare(BlockDriverState *bs,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* get L2 table/refcount block cache size from command line options */
|
/* get L2 table/refcount block cache size from command line options */
|
||||||
read_cache_sizes(bs, opts, &l2_cache_size, &refcount_cache_size,
|
read_cache_sizes(bs, opts, &l2_cache_size, &l2_cache_entry_size,
|
||||||
&local_err);
|
&refcount_cache_size, &local_err);
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
error_propagate(errp, local_err);
|
error_propagate(errp, local_err);
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
l2_cache_size /= s->cluster_size;
|
l2_cache_size /= l2_cache_entry_size;
|
||||||
if (l2_cache_size < MIN_L2_CACHE_SIZE) {
|
if (l2_cache_size < MIN_L2_CACHE_SIZE) {
|
||||||
l2_cache_size = MIN_L2_CACHE_SIZE;
|
l2_cache_size = MIN_L2_CACHE_SIZE;
|
||||||
}
|
}
|
||||||
|
@ -888,8 +907,11 @@ static int qcow2_update_options_prepare(BlockDriverState *bs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r->l2_table_cache = qcow2_cache_create(bs, l2_cache_size);
|
r->l2_slice_size = l2_cache_entry_size / sizeof(uint64_t);
|
||||||
r->refcount_block_cache = qcow2_cache_create(bs, refcount_cache_size);
|
r->l2_table_cache = qcow2_cache_create(bs, l2_cache_size,
|
||||||
|
l2_cache_entry_size);
|
||||||
|
r->refcount_block_cache = qcow2_cache_create(bs, refcount_cache_size,
|
||||||
|
s->cluster_size);
|
||||||
if (r->l2_table_cache == NULL || r->refcount_block_cache == NULL) {
|
if (r->l2_table_cache == NULL || r->refcount_block_cache == NULL) {
|
||||||
error_setg(errp, "Could not allocate metadata caches");
|
error_setg(errp, "Could not allocate metadata caches");
|
||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
|
@ -1044,13 +1066,14 @@ static void qcow2_update_options_commit(BlockDriverState *bs,
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (s->l2_table_cache) {
|
if (s->l2_table_cache) {
|
||||||
qcow2_cache_destroy(bs, s->l2_table_cache);
|
qcow2_cache_destroy(s->l2_table_cache);
|
||||||
}
|
}
|
||||||
if (s->refcount_block_cache) {
|
if (s->refcount_block_cache) {
|
||||||
qcow2_cache_destroy(bs, s->refcount_block_cache);
|
qcow2_cache_destroy(s->refcount_block_cache);
|
||||||
}
|
}
|
||||||
s->l2_table_cache = r->l2_table_cache;
|
s->l2_table_cache = r->l2_table_cache;
|
||||||
s->refcount_block_cache = r->refcount_block_cache;
|
s->refcount_block_cache = r->refcount_block_cache;
|
||||||
|
s->l2_slice_size = r->l2_slice_size;
|
||||||
|
|
||||||
s->overlap_check = r->overlap_check;
|
s->overlap_check = r->overlap_check;
|
||||||
s->use_lazy_refcounts = r->use_lazy_refcounts;
|
s->use_lazy_refcounts = r->use_lazy_refcounts;
|
||||||
|
@ -1073,10 +1096,10 @@ static void qcow2_update_options_abort(BlockDriverState *bs,
|
||||||
Qcow2ReopenState *r)
|
Qcow2ReopenState *r)
|
||||||
{
|
{
|
||||||
if (r->l2_table_cache) {
|
if (r->l2_table_cache) {
|
||||||
qcow2_cache_destroy(bs, r->l2_table_cache);
|
qcow2_cache_destroy(r->l2_table_cache);
|
||||||
}
|
}
|
||||||
if (r->refcount_block_cache) {
|
if (r->refcount_block_cache) {
|
||||||
qcow2_cache_destroy(bs, r->refcount_block_cache);
|
qcow2_cache_destroy(r->refcount_block_cache);
|
||||||
}
|
}
|
||||||
qapi_free_QCryptoBlockOpenOptions(r->crypto_opts);
|
qapi_free_QCryptoBlockOpenOptions(r->crypto_opts);
|
||||||
}
|
}
|
||||||
|
@ -1460,7 +1483,7 @@ static int qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
s->autoclear_features &= QCOW2_AUTOCLEAR_MASK;
|
s->autoclear_features &= QCOW2_AUTOCLEAR_MASK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qcow2_load_autoloading_dirty_bitmaps(bs, &local_err)) {
|
if (qcow2_load_dirty_bitmaps(bs, &local_err)) {
|
||||||
update_header = false;
|
update_header = false;
|
||||||
}
|
}
|
||||||
if (local_err != NULL) {
|
if (local_err != NULL) {
|
||||||
|
@ -1514,10 +1537,10 @@ static int qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
s->l1_table = NULL;
|
s->l1_table = NULL;
|
||||||
cache_clean_timer_del(bs);
|
cache_clean_timer_del(bs);
|
||||||
if (s->l2_table_cache) {
|
if (s->l2_table_cache) {
|
||||||
qcow2_cache_destroy(bs, s->l2_table_cache);
|
qcow2_cache_destroy(s->l2_table_cache);
|
||||||
}
|
}
|
||||||
if (s->refcount_block_cache) {
|
if (s->refcount_block_cache) {
|
||||||
qcow2_cache_destroy(bs, s->refcount_block_cache);
|
qcow2_cache_destroy(s->refcount_block_cache);
|
||||||
}
|
}
|
||||||
qcrypto_block_free(s->crypto);
|
qcrypto_block_free(s->crypto);
|
||||||
qapi_free_QCryptoBlockOpenOptions(s->crypto_opts);
|
qapi_free_QCryptoBlockOpenOptions(s->crypto_opts);
|
||||||
|
@ -2065,8 +2088,8 @@ static void qcow2_close(BlockDriverState *bs)
|
||||||
}
|
}
|
||||||
|
|
||||||
cache_clean_timer_del(bs);
|
cache_clean_timer_del(bs);
|
||||||
qcow2_cache_destroy(bs, s->l2_table_cache);
|
qcow2_cache_destroy(s->l2_table_cache);
|
||||||
qcow2_cache_destroy(bs, s->refcount_block_cache);
|
qcow2_cache_destroy(s->refcount_block_cache);
|
||||||
|
|
||||||
qcrypto_block_free(s->crypto);
|
qcrypto_block_free(s->crypto);
|
||||||
s->crypto = NULL;
|
s->crypto = NULL;
|
||||||
|
@ -3259,9 +3282,9 @@ static int qcow2_truncate(BlockDriverState *bs, int64_t offset,
|
||||||
host_offset = allocation_start;
|
host_offset = allocation_start;
|
||||||
guest_offset = old_length;
|
guest_offset = old_length;
|
||||||
while (nb_new_data_clusters) {
|
while (nb_new_data_clusters) {
|
||||||
int64_t guest_cluster = guest_offset >> s->cluster_bits;
|
int64_t nb_clusters = MIN(
|
||||||
int64_t nb_clusters = MIN(nb_new_data_clusters,
|
nb_new_data_clusters,
|
||||||
s->l2_size - guest_cluster % s->l2_size);
|
s->l2_slice_size - offset_to_l2_slice_index(s, guest_offset));
|
||||||
QCowL2Meta allocation = {
|
QCowL2Meta allocation = {
|
||||||
.offset = guest_offset,
|
.offset = guest_offset,
|
||||||
.alloc_offset = host_offset,
|
.alloc_offset = host_offset,
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
#define MAX_CLUSTER_BITS 21
|
#define MAX_CLUSTER_BITS 21
|
||||||
|
|
||||||
/* Must be at least 2 to cover COW */
|
/* Must be at least 2 to cover COW */
|
||||||
#define MIN_L2_CACHE_SIZE 2 /* clusters */
|
#define MIN_L2_CACHE_SIZE 2 /* cache entries */
|
||||||
|
|
||||||
/* Must be at least 4 to cover all cases of refcount table growth */
|
/* Must be at least 4 to cover all cases of refcount table growth */
|
||||||
#define MIN_REFCOUNT_CACHE_SIZE 4 /* clusters */
|
#define MIN_REFCOUNT_CACHE_SIZE 4 /* clusters */
|
||||||
|
@ -100,6 +100,7 @@
|
||||||
#define QCOW2_OPT_OVERLAP_INACTIVE_L2 "overlap-check.inactive-l2"
|
#define QCOW2_OPT_OVERLAP_INACTIVE_L2 "overlap-check.inactive-l2"
|
||||||
#define QCOW2_OPT_CACHE_SIZE "cache-size"
|
#define QCOW2_OPT_CACHE_SIZE "cache-size"
|
||||||
#define QCOW2_OPT_L2_CACHE_SIZE "l2-cache-size"
|
#define QCOW2_OPT_L2_CACHE_SIZE "l2-cache-size"
|
||||||
|
#define QCOW2_OPT_L2_CACHE_ENTRY_SIZE "l2-cache-entry-size"
|
||||||
#define QCOW2_OPT_REFCOUNT_CACHE_SIZE "refcount-cache-size"
|
#define QCOW2_OPT_REFCOUNT_CACHE_SIZE "refcount-cache-size"
|
||||||
#define QCOW2_OPT_CACHE_CLEAN_INTERVAL "cache-clean-interval"
|
#define QCOW2_OPT_CACHE_CLEAN_INTERVAL "cache-clean-interval"
|
||||||
|
|
||||||
|
@ -251,6 +252,7 @@ typedef struct BDRVQcow2State {
|
||||||
int cluster_bits;
|
int cluster_bits;
|
||||||
int cluster_size;
|
int cluster_size;
|
||||||
int cluster_sectors;
|
int cluster_sectors;
|
||||||
|
int l2_slice_size;
|
||||||
int l2_bits;
|
int l2_bits;
|
||||||
int l2_size;
|
int l2_size;
|
||||||
int l1_size;
|
int l1_size;
|
||||||
|
@ -463,11 +465,21 @@ static inline int64_t size_to_l1(BDRVQcow2State *s, int64_t size)
|
||||||
return (size + (1ULL << shift) - 1) >> shift;
|
return (size + (1ULL << shift) - 1) >> shift;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline int offset_to_l1_index(BDRVQcow2State *s, uint64_t offset)
|
||||||
|
{
|
||||||
|
return offset >> (s->l2_bits + s->cluster_bits);
|
||||||
|
}
|
||||||
|
|
||||||
static inline int offset_to_l2_index(BDRVQcow2State *s, int64_t offset)
|
static inline int offset_to_l2_index(BDRVQcow2State *s, int64_t offset)
|
||||||
{
|
{
|
||||||
return (offset >> s->cluster_bits) & (s->l2_size - 1);
|
return (offset >> s->cluster_bits) & (s->l2_size - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline int offset_to_l2_slice_index(BDRVQcow2State *s, int64_t offset)
|
||||||
|
{
|
||||||
|
return (offset >> s->cluster_bits) & (s->l2_slice_size - 1);
|
||||||
|
}
|
||||||
|
|
||||||
static inline int64_t align_offset(int64_t offset, int n)
|
static inline int64_t align_offset(int64_t offset, int n)
|
||||||
{
|
{
|
||||||
offset = (offset + n - 1) & ~(n - 1);
|
offset = (offset + n - 1) & ~(n - 1);
|
||||||
|
@ -636,34 +648,33 @@ void qcow2_free_snapshots(BlockDriverState *bs);
|
||||||
int qcow2_read_snapshots(BlockDriverState *bs);
|
int qcow2_read_snapshots(BlockDriverState *bs);
|
||||||
|
|
||||||
/* qcow2-cache.c functions */
|
/* qcow2-cache.c functions */
|
||||||
Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables);
|
Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables,
|
||||||
int qcow2_cache_destroy(BlockDriverState* bs, Qcow2Cache *c);
|
unsigned table_size);
|
||||||
|
int qcow2_cache_destroy(Qcow2Cache *c);
|
||||||
|
|
||||||
void qcow2_cache_entry_mark_dirty(BlockDriverState *bs, Qcow2Cache *c,
|
void qcow2_cache_entry_mark_dirty(Qcow2Cache *c, void *table);
|
||||||
void *table);
|
|
||||||
int qcow2_cache_flush(BlockDriverState *bs, Qcow2Cache *c);
|
int qcow2_cache_flush(BlockDriverState *bs, Qcow2Cache *c);
|
||||||
int qcow2_cache_write(BlockDriverState *bs, Qcow2Cache *c);
|
int qcow2_cache_write(BlockDriverState *bs, Qcow2Cache *c);
|
||||||
int qcow2_cache_set_dependency(BlockDriverState *bs, Qcow2Cache *c,
|
int qcow2_cache_set_dependency(BlockDriverState *bs, Qcow2Cache *c,
|
||||||
Qcow2Cache *dependency);
|
Qcow2Cache *dependency);
|
||||||
void qcow2_cache_depends_on_flush(Qcow2Cache *c);
|
void qcow2_cache_depends_on_flush(Qcow2Cache *c);
|
||||||
|
|
||||||
void qcow2_cache_clean_unused(BlockDriverState *bs, Qcow2Cache *c);
|
void qcow2_cache_clean_unused(Qcow2Cache *c);
|
||||||
int qcow2_cache_empty(BlockDriverState *bs, Qcow2Cache *c);
|
int qcow2_cache_empty(BlockDriverState *bs, Qcow2Cache *c);
|
||||||
|
|
||||||
int qcow2_cache_get(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset,
|
int qcow2_cache_get(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset,
|
||||||
void **table);
|
void **table);
|
||||||
int qcow2_cache_get_empty(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset,
|
int qcow2_cache_get_empty(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset,
|
||||||
void **table);
|
void **table);
|
||||||
void qcow2_cache_put(BlockDriverState *bs, Qcow2Cache *c, void **table);
|
void qcow2_cache_put(Qcow2Cache *c, void **table);
|
||||||
void *qcow2_cache_is_table_offset(BlockDriverState *bs, Qcow2Cache *c,
|
void *qcow2_cache_is_table_offset(Qcow2Cache *c, uint64_t offset);
|
||||||
uint64_t offset);
|
void qcow2_cache_discard(Qcow2Cache *c, void *table);
|
||||||
void qcow2_cache_discard(BlockDriverState *bs, Qcow2Cache *c, void *table);
|
|
||||||
|
|
||||||
/* qcow2-bitmap.c functions */
|
/* qcow2-bitmap.c functions */
|
||||||
int qcow2_check_bitmaps_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
int qcow2_check_bitmaps_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||||
void **refcount_table,
|
void **refcount_table,
|
||||||
int64_t *refcount_table_size);
|
int64_t *refcount_table_size);
|
||||||
bool qcow2_load_autoloading_dirty_bitmaps(BlockDriverState *bs, Error **errp);
|
bool qcow2_load_dirty_bitmaps(BlockDriverState *bs, Error **errp);
|
||||||
int qcow2_reopen_bitmaps_rw(BlockDriverState *bs, Error **errp);
|
int qcow2_reopen_bitmaps_rw(BlockDriverState *bs, Error **errp);
|
||||||
void qcow2_store_persistent_dirty_bitmaps(BlockDriverState *bs, Error **errp);
|
void qcow2_store_persistent_dirty_bitmaps(BlockDriverState *bs, Error **errp);
|
||||||
int qcow2_reopen_bitmaps_ro(BlockDriverState *bs, Error **errp);
|
int qcow2_reopen_bitmaps_ro(BlockDriverState *bs, Error **errp);
|
||||||
|
|
10
blockdev.c
10
blockdev.c
|
@ -2825,14 +2825,9 @@ void qmp_block_dirty_bitmap_add(const char *node, const char *name,
|
||||||
if (!has_persistent) {
|
if (!has_persistent) {
|
||||||
persistent = false;
|
persistent = false;
|
||||||
}
|
}
|
||||||
if (!has_autoload) {
|
|
||||||
autoload = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (has_autoload && !persistent) {
|
if (has_autoload) {
|
||||||
error_setg(errp, "Autoload flag must be used only for persistent "
|
warn_report("Autoload option is deprecated and its value is ignored");
|
||||||
"bitmaps");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (persistent &&
|
if (persistent &&
|
||||||
|
@ -2847,7 +2842,6 @@ void qmp_block_dirty_bitmap_add(const char *node, const char *name,
|
||||||
}
|
}
|
||||||
|
|
||||||
bdrv_dirty_bitmap_set_persistance(bitmap, persistent);
|
bdrv_dirty_bitmap_set_persistance(bitmap, persistent);
|
||||||
bdrv_dirty_bitmap_set_autoload(bitmap, autoload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void qmp_block_dirty_bitmap_remove(const char *node, const char *name,
|
void qmp_block_dirty_bitmap_remove(const char *node, const char *name,
|
||||||
|
|
|
@ -66,7 +66,6 @@ void bdrv_dirty_bitmap_deserialize_ones(BdrvDirtyBitmap *bitmap,
|
||||||
void bdrv_dirty_bitmap_deserialize_finish(BdrvDirtyBitmap *bitmap);
|
void bdrv_dirty_bitmap_deserialize_finish(BdrvDirtyBitmap *bitmap);
|
||||||
|
|
||||||
void bdrv_dirty_bitmap_set_readonly(BdrvDirtyBitmap *bitmap, bool value);
|
void bdrv_dirty_bitmap_set_readonly(BdrvDirtyBitmap *bitmap, bool value);
|
||||||
void bdrv_dirty_bitmap_set_autoload(BdrvDirtyBitmap *bitmap, bool autoload);
|
|
||||||
void bdrv_dirty_bitmap_set_persistance(BdrvDirtyBitmap *bitmap,
|
void bdrv_dirty_bitmap_set_persistance(BdrvDirtyBitmap *bitmap,
|
||||||
bool persistent);
|
bool persistent);
|
||||||
|
|
||||||
|
|
|
@ -1593,9 +1593,9 @@
|
||||||
# Qcow2 disks support persistent bitmaps. Default is false for
|
# Qcow2 disks support persistent bitmaps. Default is false for
|
||||||
# block-dirty-bitmap-add. (Since: 2.10)
|
# block-dirty-bitmap-add. (Since: 2.10)
|
||||||
#
|
#
|
||||||
# @autoload: the bitmap will be automatically loaded when the image it is stored
|
# @autoload: ignored and deprecated since 2.12.
|
||||||
# in is opened. This flag may only be specified for persistent
|
# Currently, all dirty tracking bitmaps are loaded from Qcow2 on
|
||||||
# bitmaps. Default is false for block-dirty-bitmap-add. (Since: 2.10)
|
# open.
|
||||||
#
|
#
|
||||||
# Since: 2.4
|
# Since: 2.4
|
||||||
##
|
##
|
||||||
|
@ -2521,6 +2521,11 @@
|
||||||
# @l2-cache-size: the maximum size of the L2 table cache in
|
# @l2-cache-size: the maximum size of the L2 table cache in
|
||||||
# bytes (since 2.2)
|
# bytes (since 2.2)
|
||||||
#
|
#
|
||||||
|
# @l2-cache-entry-size: the size of each entry in the L2 cache in
|
||||||
|
# bytes. It must be a power of two between 512
|
||||||
|
# and the cluster size. The default value is
|
||||||
|
# the cluster size (since 2.12)
|
||||||
|
#
|
||||||
# @refcount-cache-size: the maximum size of the refcount block cache
|
# @refcount-cache-size: the maximum size of the refcount block cache
|
||||||
# in bytes (since 2.2)
|
# in bytes (since 2.2)
|
||||||
#
|
#
|
||||||
|
@ -2542,6 +2547,7 @@
|
||||||
'*overlap-check': 'Qcow2OverlapChecks',
|
'*overlap-check': 'Qcow2OverlapChecks',
|
||||||
'*cache-size': 'int',
|
'*cache-size': 'int',
|
||||||
'*l2-cache-size': 'int',
|
'*l2-cache-size': 'int',
|
||||||
|
'*l2-cache-entry-size': 'int',
|
||||||
'*refcount-cache-size': 'int',
|
'*refcount-cache-size': 'int',
|
||||||
'*cache-clean-interval': 'int',
|
'*cache-clean-interval': 'int',
|
||||||
'*encrypt': 'BlockdevQcow2Encryption' } }
|
'*encrypt': 'BlockdevQcow2Encryption' } }
|
||||||
|
|
|
@ -2757,6 +2757,13 @@ used and it will be removed with no replacement.
|
||||||
The ``convert -s snapshot_id_or_name'' argument is obsoleted
|
The ``convert -s snapshot_id_or_name'' argument is obsoleted
|
||||||
by the ``convert -l snapshot_param'' argument instead.
|
by the ``convert -l snapshot_param'' argument instead.
|
||||||
|
|
||||||
|
@section QEMU Machine Protocol (QMP) commands
|
||||||
|
|
||||||
|
@subsection block-dirty-bitmap-add "autoload" parameter (since 2.12.0)
|
||||||
|
|
||||||
|
"autoload" parameter is now ignored. All bitmaps are automatically loaded
|
||||||
|
from qcow2 images.
|
||||||
|
|
||||||
@section System emulator human monitor commands
|
@section System emulator human monitor commands
|
||||||
|
|
||||||
@subsection host_net_add (since 2.10.0)
|
@subsection host_net_add (since 2.10.0)
|
||||||
|
|
|
@ -53,6 +53,22 @@ $PYTHON qcow2.py "$TEST_IMG" dump-header
|
||||||
$QEMU_IO -c "read -P 0 0 128k" "$TEST_IMG" | _filter_qemu_io
|
$QEMU_IO -c "read -P 0 0 128k" "$TEST_IMG" | _filter_qemu_io
|
||||||
_check_test_img
|
_check_test_img
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Testing version downgrade with zero expansion and 4K cache entries ==="
|
||||||
|
echo
|
||||||
|
IMGOPTS="compat=1.1,lazy_refcounts=on" _make_test_img 64M
|
||||||
|
$QEMU_IO -c "write -z 0 128k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
$QEMU_IO -c "write -z 32M 128k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
$QEMU_IO -c map "$TEST_IMG" | _filter_qemu_io
|
||||||
|
$PYTHON qcow2.py "$TEST_IMG" dump-header
|
||||||
|
$QEMU_IMG amend -o "compat=0.10" --image-opts \
|
||||||
|
driver=qcow2,file.filename=$TEST_IMG,l2-cache-entry-size=4096
|
||||||
|
$PYTHON qcow2.py "$TEST_IMG" dump-header
|
||||||
|
$QEMU_IO -c "read -P 0 0 128k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
$QEMU_IO -c "read -P 0 32M 128k" "$TEST_IMG" | _filter_qemu_io
|
||||||
|
$QEMU_IO -c map "$TEST_IMG" | _filter_qemu_io
|
||||||
|
_check_test_img
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "=== Testing dirty version downgrade ==="
|
echo "=== Testing dirty version downgrade ==="
|
||||||
echo
|
echo
|
||||||
|
|
|
@ -52,6 +52,67 @@ read 131072/131072 bytes at offset 0
|
||||||
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
No errors were found on the image.
|
No errors were found on the image.
|
||||||
|
|
||||||
|
=== Testing version downgrade with zero expansion and 4K cache entries ===
|
||||||
|
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||||
|
wrote 131072/131072 bytes at offset 0
|
||||||
|
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
wrote 131072/131072 bytes at offset 33554432
|
||||||
|
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
128 KiB (0x20000) bytes allocated at offset 0 bytes (0x0)
|
||||||
|
31.875 MiB (0x1fe0000) bytes not allocated at offset 128 KiB (0x20000)
|
||||||
|
128 KiB (0x20000) bytes allocated at offset 32 MiB (0x2000000)
|
||||||
|
31.875 MiB (0x1fe0000) bytes not allocated at offset 32.125 MiB (0x2020000)
|
||||||
|
magic 0x514649fb
|
||||||
|
version 3
|
||||||
|
backing_file_offset 0x0
|
||||||
|
backing_file_size 0x0
|
||||||
|
cluster_bits 16
|
||||||
|
size 67108864
|
||||||
|
crypt_method 0
|
||||||
|
l1_size 1
|
||||||
|
l1_table_offset 0x30000
|
||||||
|
refcount_table_offset 0x10000
|
||||||
|
refcount_table_clusters 1
|
||||||
|
nb_snapshots 0
|
||||||
|
snapshot_offset 0x0
|
||||||
|
incompatible_features 0x0
|
||||||
|
compatible_features 0x1
|
||||||
|
autoclear_features 0x0
|
||||||
|
refcount_order 4
|
||||||
|
header_length 104
|
||||||
|
|
||||||
|
Header extension:
|
||||||
|
magic 0x6803f857
|
||||||
|
length 144
|
||||||
|
data <binary>
|
||||||
|
|
||||||
|
magic 0x514649fb
|
||||||
|
version 2
|
||||||
|
backing_file_offset 0x0
|
||||||
|
backing_file_size 0x0
|
||||||
|
cluster_bits 16
|
||||||
|
size 67108864
|
||||||
|
crypt_method 0
|
||||||
|
l1_size 1
|
||||||
|
l1_table_offset 0x30000
|
||||||
|
refcount_table_offset 0x10000
|
||||||
|
refcount_table_clusters 1
|
||||||
|
nb_snapshots 0
|
||||||
|
snapshot_offset 0x0
|
||||||
|
incompatible_features 0x0
|
||||||
|
compatible_features 0x0
|
||||||
|
autoclear_features 0x0
|
||||||
|
refcount_order 4
|
||||||
|
header_length 72
|
||||||
|
|
||||||
|
read 131072/131072 bytes at offset 0
|
||||||
|
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read 131072/131072 bytes at offset 33554432
|
||||||
|
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
64 MiB (0x4000000) bytes not allocated at offset 0 bytes (0x0)
|
||||||
|
No errors were found on the image.
|
||||||
|
|
||||||
=== Testing dirty version downgrade ===
|
=== Testing dirty version downgrade ===
|
||||||
|
|
||||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
|
||||||
|
|
|
@ -66,6 +66,14 @@ $QEMU_IO -c "open -o cache-size=1M,refcount-cache-size=2M $TEST_IMG" 2>&1 \
|
||||||
$QEMU_IO -c "open -o cache-size=0,l2-cache-size=0,refcount-cache-size=0 $TEST_IMG" \
|
$QEMU_IO -c "open -o cache-size=0,l2-cache-size=0,refcount-cache-size=0 $TEST_IMG" \
|
||||||
2>&1 | _filter_testdir | _filter_imgfmt
|
2>&1 | _filter_testdir | _filter_imgfmt
|
||||||
|
|
||||||
|
# Invalid cache entry sizes
|
||||||
|
$QEMU_IO -c "open -o l2-cache-entry-size=256 $TEST_IMG" \
|
||||||
|
2>&1 | _filter_testdir | _filter_imgfmt
|
||||||
|
$QEMU_IO -c "open -o l2-cache-entry-size=4242 $TEST_IMG" \
|
||||||
|
2>&1 | _filter_testdir | _filter_imgfmt
|
||||||
|
$QEMU_IO -c "open -o l2-cache-entry-size=128k $TEST_IMG" \
|
||||||
|
2>&1 | _filter_testdir | _filter_imgfmt
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo '=== Testing valid option combinations ==='
|
echo '=== Testing valid option combinations ==='
|
||||||
echo
|
echo
|
||||||
|
@ -94,6 +102,15 @@ $QEMU_IO -c "open -o l2-cache-size=1M,refcount-cache-size=0.25M $TEST_IMG" \
|
||||||
-c 'read -P 42 0 64k' \
|
-c 'read -P 42 0 64k' \
|
||||||
| _filter_qemu_io
|
| _filter_qemu_io
|
||||||
|
|
||||||
|
# Valid cache entry sizes
|
||||||
|
$QEMU_IO -c "open -o l2-cache-entry-size=512 $TEST_IMG" \
|
||||||
|
2>&1 | _filter_testdir | _filter_imgfmt
|
||||||
|
$QEMU_IO -c "open -o l2-cache-entry-size=16k $TEST_IMG" \
|
||||||
|
2>&1 | _filter_testdir | _filter_imgfmt
|
||||||
|
$QEMU_IO -c "open -o l2-cache-entry-size=64k $TEST_IMG" \
|
||||||
|
2>&1 | _filter_testdir | _filter_imgfmt
|
||||||
|
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo '=== Testing minimal L2 cache and COW ==='
|
echo '=== Testing minimal L2 cache and COW ==='
|
||||||
echo
|
echo
|
||||||
|
|
|
@ -9,6 +9,9 @@ can't open device TEST_DIR/t.IMGFMT: cache-size, l2-cache-size and refcount-cach
|
||||||
can't open device TEST_DIR/t.IMGFMT: l2-cache-size may not exceed cache-size
|
can't open device TEST_DIR/t.IMGFMT: l2-cache-size may not exceed cache-size
|
||||||
can't open device TEST_DIR/t.IMGFMT: refcount-cache-size may not exceed cache-size
|
can't open device TEST_DIR/t.IMGFMT: refcount-cache-size may not exceed cache-size
|
||||||
can't open device TEST_DIR/t.IMGFMT: cache-size, l2-cache-size and refcount-cache-size may not be set the same time
|
can't open device TEST_DIR/t.IMGFMT: cache-size, l2-cache-size and refcount-cache-size may not be set the same time
|
||||||
|
can't open device TEST_DIR/t.IMGFMT: L2 cache entry size must be a power of two between 512 and the cluster size (65536)
|
||||||
|
can't open device TEST_DIR/t.IMGFMT: L2 cache entry size must be a power of two between 512 and the cluster size (65536)
|
||||||
|
can't open device TEST_DIR/t.IMGFMT: L2 cache entry size must be a power of two between 512 and the cluster size (65536)
|
||||||
|
|
||||||
=== Testing valid option combinations ===
|
=== Testing valid option combinations ===
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,9 @@ $QEMU_IO \
|
||||||
-c "reopen -o overlap-check.inactive-l2=off" \
|
-c "reopen -o overlap-check.inactive-l2=off" \
|
||||||
-c "reopen -o cache-size=1M" \
|
-c "reopen -o cache-size=1M" \
|
||||||
-c "reopen -o l2-cache-size=512k" \
|
-c "reopen -o l2-cache-size=512k" \
|
||||||
|
-c "reopen -o l2-cache-entry-size=512" \
|
||||||
|
-c "reopen -o l2-cache-entry-size=4k" \
|
||||||
|
-c "reopen -o l2-cache-entry-size=64k" \
|
||||||
-c "reopen -o refcount-cache-size=128k" \
|
-c "reopen -o refcount-cache-size=128k" \
|
||||||
-c "reopen -o cache-clean-interval=5" \
|
-c "reopen -o cache-clean-interval=5" \
|
||||||
-c "reopen -o cache-clean-interval=0" \
|
-c "reopen -o cache-clean-interval=0" \
|
||||||
|
@ -107,6 +110,8 @@ $QEMU_IO \
|
||||||
-c "reopen -o cache-size=1M,l2-cache-size=2M" \
|
-c "reopen -o cache-size=1M,l2-cache-size=2M" \
|
||||||
-c "reopen -o cache-size=1M,refcount-cache-size=2M" \
|
-c "reopen -o cache-size=1M,refcount-cache-size=2M" \
|
||||||
-c "reopen -o l2-cache-size=256T" \
|
-c "reopen -o l2-cache-size=256T" \
|
||||||
|
-c "reopen -o l2-cache-entry-size=33k" \
|
||||||
|
-c "reopen -o l2-cache-entry-size=128k" \
|
||||||
-c "reopen -o refcount-cache-size=256T" \
|
-c "reopen -o refcount-cache-size=256T" \
|
||||||
-c "reopen -o overlap-check=constant,overlap-check.template=all" \
|
-c "reopen -o overlap-check=constant,overlap-check.template=all" \
|
||||||
-c "reopen -o overlap-check=blubb" \
|
-c "reopen -o overlap-check=blubb" \
|
||||||
|
|
|
@ -20,6 +20,8 @@ cache-size, l2-cache-size and refcount-cache-size may not be set the same time
|
||||||
l2-cache-size may not exceed cache-size
|
l2-cache-size may not exceed cache-size
|
||||||
refcount-cache-size may not exceed cache-size
|
refcount-cache-size may not exceed cache-size
|
||||||
L2 cache size too big
|
L2 cache size too big
|
||||||
|
L2 cache entry size must be a power of two between 512 and the cluster size (65536)
|
||||||
|
L2 cache entry size must be a power of two between 512 and the cluster size (65536)
|
||||||
L2 cache size too big
|
L2 cache size too big
|
||||||
Conflicting values for qcow2 options 'overlap-check' ('constant') and 'overlap-check.template' ('all')
|
Conflicting values for qcow2 options 'overlap-check' ('constant') and 'overlap-check.template' ('all')
|
||||||
Unsupported value 'blubb' for qcow2 option 'overlap-check'. Allowed are any of the following: none, constant, cached, all
|
Unsupported value 'blubb' for qcow2 option 'overlap-check'. Allowed are any of the following: none, constant, cached, all
|
||||||
|
|
|
@ -64,7 +64,7 @@ class TestPersistentDirtyBitmap(iotests.QMPTestCase):
|
||||||
|
|
||||||
def qmpAddBitmap(self):
|
def qmpAddBitmap(self):
|
||||||
self.vm.qmp('block-dirty-bitmap-add', node='drive0',
|
self.vm.qmp('block-dirty-bitmap-add', node='drive0',
|
||||||
name='bitmap0', persistent=True, autoload=True)
|
name='bitmap0', persistent=True)
|
||||||
|
|
||||||
def test_persistent(self):
|
def test_persistent(self):
|
||||||
self.vm = self.mkVm()
|
self.vm = self.mkVm()
|
||||||
|
|
|
@ -95,7 +95,7 @@ case $reason in
|
||||||
"file": { "driver": "file", "filename": "$TEST_IMG" } } }
|
"file": { "driver": "file", "filename": "$TEST_IMG" } } }
|
||||||
{ "execute": "block-dirty-bitmap-add",
|
{ "execute": "block-dirty-bitmap-add",
|
||||||
"arguments": { "node": "drive0", "name": "bitmap0",
|
"arguments": { "node": "drive0", "name": "bitmap0",
|
||||||
"persistent": true, "autoload": true } }
|
"persistent": true } }
|
||||||
{ "execute": "quit" }
|
{ "execute": "quit" }
|
||||||
EOF
|
EOF
|
||||||
;;
|
;;
|
||||||
|
|
Loading…
Reference in New Issue