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
|
||||
and this bitmap must remain unchanged while
|
||||
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 */
|
||||
QLIST_ENTRY(BdrvDirtyBitmap) list;
|
||||
};
|
||||
|
@ -104,7 +102,6 @@ void bdrv_dirty_bitmap_make_anon(BdrvDirtyBitmap *bitmap)
|
|||
g_free(bitmap->name);
|
||||
bitmap->name = NULL;
|
||||
bitmap->persistent = false;
|
||||
bitmap->autoload = false;
|
||||
}
|
||||
|
||||
/* Called with BQL taken. */
|
||||
|
@ -261,8 +258,6 @@ BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
|
|||
bitmap->successor = NULL;
|
||||
successor->persistent = bitmap->persistent;
|
||||
bitmap->persistent = false;
|
||||
successor->autoload = bitmap->autoload;
|
||||
bitmap->autoload = false;
|
||||
bdrv_release_dirty_bitmap(bs, bitmap);
|
||||
|
||||
return successor;
|
||||
|
@ -666,19 +661,6 @@ bool bdrv_has_readonly_bitmaps(BlockDriverState *bs)
|
|||
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. */
|
||||
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);
|
||||
}
|
||||
|
||||
/* qcow2_load_autoloading_dirty_bitmaps()
|
||||
/* qcow2_load_dirty_bitmaps()
|
||||
* 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
|
||||
* caller, it just means that updating was not needed or the image cannot be
|
||||
* written to).
|
||||
* 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;
|
||||
Qcow2BitmapList *bm_list;
|
||||
|
@ -960,14 +960,16 @@ bool qcow2_load_autoloading_dirty_bitmaps(BlockDriverState *bs, Error **errp)
|
|||
}
|
||||
|
||||
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);
|
||||
if (bitmap == NULL) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!(bm->flags & BME_FLAG_AUTO)) {
|
||||
bdrv_disable_dirty_bitmap(bitmap);
|
||||
}
|
||||
bdrv_dirty_bitmap_set_persistance(bitmap, true);
|
||||
bdrv_dirty_bitmap_set_autoload(bitmap, true);
|
||||
bm->flags |= BME_FLAG_IN_USE;
|
||||
created_dirty_bitmaps =
|
||||
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;
|
||||
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->dirty_bitmap = bitmap;
|
||||
}
|
||||
|
|
|
@ -39,26 +39,23 @@ struct Qcow2Cache {
|
|||
Qcow2CachedTable *entries;
|
||||
struct Qcow2Cache *depends;
|
||||
int size;
|
||||
int table_size;
|
||||
bool depends_on_flush;
|
||||
void *table_array;
|
||||
uint64_t lru_counter;
|
||||
uint64_t cache_clean_lru_counter;
|
||||
};
|
||||
|
||||
static inline void *qcow2_cache_get_table_addr(BlockDriverState *bs,
|
||||
Qcow2Cache *c, int table)
|
||||
static inline void *qcow2_cache_get_table_addr(Qcow2Cache *c, int table)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
return (uint8_t *) c->table_array + (size_t) table * s->cluster_size;
|
||||
return (uint8_t *) c->table_array + (size_t) table * c->table_size;
|
||||
}
|
||||
|
||||
static inline int qcow2_cache_get_table_idx(BlockDriverState *bs,
|
||||
Qcow2Cache *c, void *table)
|
||||
static inline int qcow2_cache_get_table_idx(Qcow2Cache *c, void *table)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
ptrdiff_t table_offset = (uint8_t *) table - (uint8_t *) c->table_array;
|
||||
int idx = table_offset / s->cluster_size;
|
||||
assert(idx >= 0 && idx < c->size && table_offset % s->cluster_size == 0);
|
||||
int idx = table_offset / c->table_size;
|
||||
assert(idx >= 0 && idx < c->size && table_offset % c->table_size == 0);
|
||||
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,
|
||||
int i, int num_tables)
|
||||
static void qcow2_cache_table_release(Qcow2Cache *c, int i, int num_tables)
|
||||
{
|
||||
/* Using MADV_DONTNEED to discard memory is a Linux-specific feature */
|
||||
#ifdef CONFIG_LINUX
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
void *t = qcow2_cache_get_table_addr(bs, c, i);
|
||||
void *t = qcow2_cache_get_table_addr(c, i);
|
||||
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 length = QEMU_ALIGN_DOWN(mem_size - offset, align);
|
||||
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;
|
||||
}
|
||||
|
||||
void qcow2_cache_clean_unused(BlockDriverState *bs, Qcow2Cache *c)
|
||||
void qcow2_cache_clean_unused(Qcow2Cache *c)
|
||||
{
|
||||
int i = 0;
|
||||
while (i < c->size) {
|
||||
|
@ -118,23 +113,30 @@ void qcow2_cache_clean_unused(BlockDriverState *bs, Qcow2Cache *c)
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
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->size = num_tables;
|
||||
c->table_size = table_size;
|
||||
c->entries = g_try_new0(Qcow2CachedTable, num_tables);
|
||||
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) {
|
||||
qemu_vfree(c->table_array);
|
||||
|
@ -146,7 +148,7 @@ Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables)
|
|||
return c;
|
||||
}
|
||||
|
||||
int qcow2_cache_destroy(BlockDriverState *bs, Qcow2Cache *c)
|
||||
int qcow2_cache_destroy(Qcow2Cache *c)
|
||||
{
|
||||
int i;
|
||||
|
||||
|
@ -203,13 +205,13 @@ static int qcow2_cache_entry_flush(BlockDriverState *bs, Qcow2Cache *c, int i)
|
|||
|
||||
if (c == s->refcount_block_cache) {
|
||||
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) {
|
||||
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 {
|
||||
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) {
|
||||
|
@ -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,
|
||||
qcow2_cache_get_table_addr(bs, c, i), s->cluster_size);
|
||||
qcow2_cache_get_table_addr(c, i), c->table_size);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
@ -309,7 +311,7 @@ int qcow2_cache_empty(BlockDriverState *bs, Qcow2Cache *c)
|
|||
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;
|
||||
|
||||
|
@ -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,
|
||||
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 "
|
||||
"cache: Offset %#" PRIx64 " is unaligned",
|
||||
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 */
|
||||
i = lookup_index = (offset / s->cluster_size * 4) % c->size;
|
||||
i = lookup_index = (offset / c->table_size * 4) % c->size;
|
||||
do {
|
||||
const Qcow2CachedTable *t = &c->entries[i];
|
||||
if (t->offset == offset) {
|
||||
|
@ -379,8 +381,8 @@ static int qcow2_cache_do_get(BlockDriverState *bs, Qcow2Cache *c,
|
|||
}
|
||||
|
||||
ret = bdrv_pread(bs->file, 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) {
|
||||
return ret;
|
||||
}
|
||||
|
@ -391,7 +393,7 @@ static int qcow2_cache_do_get(BlockDriverState *bs, Qcow2Cache *c,
|
|||
/* And return the right table */
|
||||
found:
|
||||
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(),
|
||||
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);
|
||||
}
|
||||
|
||||
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--;
|
||||
*table = NULL;
|
||||
|
@ -425,30 +427,28 @@ void qcow2_cache_put(BlockDriverState *bs, Qcow2Cache *c, void **table)
|
|||
assert(c->entries[i].ref >= 0);
|
||||
}
|
||||
|
||||
void qcow2_cache_entry_mark_dirty(BlockDriverState *bs, Qcow2Cache *c,
|
||||
void *table)
|
||||
void qcow2_cache_entry_mark_dirty(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].offset != 0);
|
||||
c->entries[i].dirty = true;
|
||||
}
|
||||
|
||||
void *qcow2_cache_is_table_offset(BlockDriverState *bs, Qcow2Cache *c,
|
||||
uint64_t offset)
|
||||
void *qcow2_cache_is_table_offset(Qcow2Cache *c, uint64_t offset)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < c->size; i++) {
|
||||
if (c->entries[i].offset == offset) {
|
||||
return qcow2_cache_get_table_addr(bs, c, i);
|
||||
return qcow2_cache_get_table_addr(c, i);
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
|
@ -456,5 +456,5 @@ void qcow2_cache_discard(BlockDriverState *bs, Qcow2Cache *c, void *table)
|
|||
c->entries[i].lru_counter = 0;
|
||||
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
|
||||
*
|
||||
* Loads a L2 table into memory. If the table is in the cache, the cache
|
||||
* is used; otherwise the L2 table is loaded from the image file.
|
||||
* @bs: The BlockDriverState
|
||||
* @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
|
||||
* the image file failed.
|
||||
* Loads a L2 slice into memory (L2 slices are the parts of L2 tables
|
||||
* 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 l2_offset,
|
||||
uint64_t **l2_table)
|
||||
static int l2_load(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t l2_offset, uint64_t **l2_slice)
|
||||
{
|
||||
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,
|
||||
(void **)l2_table);
|
||||
return qcow2_cache_get(bs, s->l2_table_cache, l2_offset + start_of_slice,
|
||||
(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;
|
||||
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;
|
||||
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 */
|
||||
|
||||
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);
|
||||
ret = qcow2_cache_get_empty(bs, s->l2_table_cache, l2_offset, (void**) table);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
l2_table = *table;
|
||||
|
||||
if ((old_l2_offset & L1E_OFFSET_MASK) == 0) {
|
||||
/* if there was no old l2 table, clear the new table */
|
||||
memset(l2_table, 0, s->l2_size * sizeof(uint64_t));
|
||||
} else {
|
||||
uint64_t* old_table;
|
||||
|
||||
/* if there was an old l2 table, read it from the disk */
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_L2_ALLOC_COW_READ);
|
||||
ret = qcow2_cache_get(bs, s->l2_table_cache,
|
||||
old_l2_offset & L1E_OFFSET_MASK,
|
||||
(void**) &old_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) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
memcpy(l2_table, old_table, s->cluster_size);
|
||||
if ((old_l2_offset & L1E_OFFSET_MASK) == 0) {
|
||||
/* if there was no old l2 table, clear the new slice */
|
||||
memset(l2_slice, 0, slice_size2);
|
||||
} else {
|
||||
uint64_t *old_slice;
|
||||
uint64_t old_l2_slice_offset =
|
||||
(old_l2_offset & L1E_OFFSET_MASK) + slice * slice_size2;
|
||||
|
||||
qcow2_cache_put(bs, s->l2_table_cache, (void **) &old_table);
|
||||
/* if there was an old l2 table, read a slice from the disk */
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_L2_ALLOC_COW_READ);
|
||||
ret = qcow2_cache_get(bs, s->l2_table_cache, old_l2_slice_offset,
|
||||
(void **) &old_slice);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
memcpy(l2_slice, old_slice, slice_size2);
|
||||
|
||||
qcow2_cache_put(s->l2_table_cache, (void **) &old_slice);
|
||||
}
|
||||
|
||||
/* write the l2 slice to the file */
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_L2_ALLOC_WRITE);
|
||||
|
||||
trace_qcow2_l2_allocate_write_l2(bs, l1_index);
|
||||
qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_slice);
|
||||
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||
}
|
||||
|
||||
/* write the l2 table to the file */
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_L2_ALLOC_WRITE);
|
||||
|
||||
trace_qcow2_l2_allocate_write_l2(bs, l1_index);
|
||||
qcow2_cache_entry_mark_dirty(bs, s->l2_table_cache, l2_table);
|
||||
ret = qcow2_cache_flush(bs, s->l2_table_cache);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
|
@ -338,14 +353,13 @@ static int l2_allocate(BlockDriverState *bs, int l1_index, uint64_t **table)
|
|||
goto fail;
|
||||
}
|
||||
|
||||
*table = l2_table;
|
||||
trace_qcow2_l2_allocate_done(bs, l1_index, 0);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
trace_qcow2_l2_allocate_done(bs, l1_index, ret);
|
||||
if (l2_table != NULL) {
|
||||
qcow2_cache_put(bs, s->l2_table_cache, (void**) table);
|
||||
if (l2_slice != NULL) {
|
||||
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||
}
|
||||
s->l1_table[l1_index] = old_l2_offset;
|
||||
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
|
||||
* 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
|
||||
* cluster which may require a different handling)
|
||||
*/
|
||||
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;
|
||||
QCow2ClusterType first_cluster_type;
|
||||
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;
|
||||
|
||||
if (!offset) {
|
||||
|
@ -381,7 +395,7 @@ static int count_contiguous_clusters(int nb_clusters, int cluster_size,
|
|||
first_cluster_type == QCOW2_CLUSTER_ZERO_ALLOC);
|
||||
|
||||
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) {
|
||||
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
|
||||
* table have the same cluster type.
|
||||
* slice have the same cluster type.
|
||||
*/
|
||||
static int count_contiguous_clusters_unallocated(int nb_clusters,
|
||||
uint64_t *l2_table,
|
||||
uint64_t *l2_slice,
|
||||
QCow2ClusterType wanted_type)
|
||||
{
|
||||
int i;
|
||||
|
@ -403,7 +417,7 @@ static int count_contiguous_clusters_unallocated(int nb_clusters,
|
|||
assert(wanted_type == QCOW2_CLUSTER_ZERO_PLAIN ||
|
||||
wanted_type == QCOW2_CLUSTER_UNALLOCATED);
|
||||
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);
|
||||
|
||||
if (type != wanted_type) {
|
||||
|
@ -515,8 +529,8 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
|||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
unsigned int l2_index;
|
||||
uint64_t l1_index, l2_offset, *l2_table;
|
||||
int l1_bits, c;
|
||||
uint64_t l1_index, l2_offset, *l2_slice;
|
||||
int c;
|
||||
unsigned int offset_in_cluster;
|
||||
uint64_t bytes_available, bytes_needed, nb_clusters;
|
||||
QCow2ClusterType type;
|
||||
|
@ -525,12 +539,12 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
|||
offset_in_cluster = offset_into_cluster(s, offset);
|
||||
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
|
||||
* containing offset and the end of the l1 entry */
|
||||
bytes_available = (1ULL << l1_bits) - (offset & ((1ULL << l1_bits) - 1))
|
||||
+ offset_in_cluster;
|
||||
* containing offset and the end of the l2 slice that contains
|
||||
* the entry pointing to it */
|
||||
bytes_available =
|
||||
((uint64_t) (s->l2_slice_size - offset_to_l2_slice_index(s, offset)))
|
||||
<< s->cluster_bits;
|
||||
|
||||
if (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 */
|
||||
|
||||
l1_index = offset >> l1_bits;
|
||||
l1_index = offset_to_l1_index(s, offset);
|
||||
if (l1_index >= s->l1_size) {
|
||||
type = QCOW2_CLUSTER_UNALLOCATED;
|
||||
goto out;
|
||||
|
@ -559,17 +573,17 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
|||
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) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* find the cluster offset for the given disk offset */
|
||||
|
||||
l2_index = offset_to_l2_index(s, offset);
|
||||
*cluster_offset = be64_to_cpu(l2_table[l2_index]);
|
||||
l2_index = offset_to_l2_slice_index(s, offset);
|
||||
*cluster_offset = be64_to_cpu(l2_slice[l2_index]);
|
||||
|
||||
nb_clusters = size_to_clusters(s, bytes_needed);
|
||||
/* 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:
|
||||
/* how many empty clusters ? */
|
||||
c = count_contiguous_clusters_unallocated(nb_clusters,
|
||||
&l2_table[l2_index], type);
|
||||
&l2_slice[l2_index], type);
|
||||
*cluster_offset = 0;
|
||||
break;
|
||||
case QCOW2_CLUSTER_ZERO_ALLOC:
|
||||
case QCOW2_CLUSTER_NORMAL:
|
||||
/* how many allocated clusters ? */
|
||||
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;
|
||||
if (offset_into_cluster(s, *cluster_offset)) {
|
||||
qcow2_signal_corruption(bs, true, -1, -1,
|
||||
|
@ -619,7 +633,7 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
|
|||
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;
|
||||
|
||||
|
@ -637,7 +651,7 @@ out:
|
|||
return type;
|
||||
|
||||
fail:
|
||||
qcow2_cache_put(bs, s->l2_table_cache, (void **)&l2_table);
|
||||
qcow2_cache_put(s->l2_table_cache, (void **)&l2_slice);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -645,26 +659,25 @@ fail:
|
|||
* get_cluster_table
|
||||
*
|
||||
* 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
|
||||
* in the l2 table are given to the caller.
|
||||
* the cluster index in the l2 slice is given to the caller.
|
||||
*
|
||||
* Returns 0 on success, -errno in failure case
|
||||
*/
|
||||
static int get_cluster_table(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t **new_l2_table,
|
||||
uint64_t **new_l2_slice,
|
||||
int *new_l2_index)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
unsigned int l2_index;
|
||||
uint64_t l1_index, l2_offset;
|
||||
uint64_t *l2_table = NULL;
|
||||
uint64_t *l2_slice = NULL;
|
||||
int ret;
|
||||
|
||||
/* 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) {
|
||||
ret = qcow2_grow_l1_table(bs, l1_index + 1, false);
|
||||
if (ret < 0) {
|
||||
|
@ -681,17 +694,9 @@ static int get_cluster_table(BlockDriverState *bs, uint64_t offset,
|
|||
return -EIO;
|
||||
}
|
||||
|
||||
/* seek the l2 table of the given l2 offset */
|
||||
|
||||
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 {
|
||||
if (!(s->l1_table[l1_index] & QCOW_OFLAG_COPIED)) {
|
||||
/* 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) {
|
||||
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_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 */
|
||||
|
||||
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;
|
||||
|
||||
return 0;
|
||||
|
@ -732,26 +747,26 @@ uint64_t qcow2_alloc_compressed_cluster_offset(BlockDriverState *bs,
|
|||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
int l2_index, ret;
|
||||
uint64_t *l2_table;
|
||||
uint64_t *l2_slice;
|
||||
int64_t cluster_offset;
|
||||
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) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Compression can't overwrite anything. Fail if the cluster was already
|
||||
* 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) {
|
||||
qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table);
|
||||
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cluster_offset = qcow2_alloc_bytes(bs, compressed_size);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -766,9 +781,9 @@ uint64_t qcow2_alloc_compressed_cluster_offset(BlockDriverState *bs,
|
|||
/* compressed clusters never have the copied flag */
|
||||
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_L2_UPDATE_COMPRESSED);
|
||||
qcow2_cache_entry_mark_dirty(bs, s->l2_table_cache, l2_table);
|
||||
l2_table[l2_index] = cpu_to_be64(cluster_offset);
|
||||
qcow2_cache_put(bs, s->l2_table_cache, (void **) &l2_table);
|
||||
qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_slice);
|
||||
l2_slice[l2_index] = cpu_to_be64(cluster_offset);
|
||||
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||
|
||||
return cluster_offset;
|
||||
}
|
||||
|
@ -907,7 +922,7 @@ int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m)
|
|||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
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++) {
|
||||
/* if two concurrent writes happen to the same unallocated cluster
|
||||
* 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
|
||||
* perform_cow()), update l2 table with its cluster pointer and free
|
||||
* old cluster. This is what this loop does */
|
||||
if (l2_table[l2_index + i] != 0) {
|
||||
old_cluster[j++] = l2_table[l2_index + i];
|
||||
if (l2_slice[l2_index + i] != 0) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
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.
|
||||
|
@ -984,12 +999,12 @@ err:
|
|||
* which must copy from the backing file)
|
||||
*/
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
switch(cluster_type) {
|
||||
|
@ -1104,7 +1119,7 @@ static int handle_copied(BlockDriverState *bs, uint64_t guest_offset,
|
|||
BDRVQcow2State *s = bs->opaque;
|
||||
int l2_index;
|
||||
uint64_t cluster_offset;
|
||||
uint64_t *l2_table;
|
||||
uint64_t *l2_slice;
|
||||
uint64_t nb_clusters;
|
||||
unsigned int keep_clusters;
|
||||
int ret;
|
||||
|
@ -1116,23 +1131,23 @@ static int handle_copied(BlockDriverState *bs, uint64_t guest_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.
|
||||
*/
|
||||
nb_clusters =
|
||||
size_to_clusters(s, offset_into_cluster(s, guest_offset) + *bytes);
|
||||
|
||||
l2_index = offset_to_l2_index(s, guest_offset);
|
||||
nb_clusters = MIN(nb_clusters, s->l2_size - l2_index);
|
||||
l2_index = offset_to_l2_slice_index(s, guest_offset);
|
||||
nb_clusters = MIN(nb_clusters, s->l2_slice_size - l2_index);
|
||||
assert(nb_clusters <= INT_MAX);
|
||||
|
||||
/* 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) {
|
||||
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 */
|
||||
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 */
|
||||
keep_clusters =
|
||||
count_contiguous_clusters(nb_clusters, s->cluster_size,
|
||||
&l2_table[l2_index],
|
||||
&l2_slice[l2_index],
|
||||
QCOW_OFLAG_COPIED | QCOW_OFLAG_ZERO);
|
||||
assert(keep_clusters <= nb_clusters);
|
||||
|
||||
|
@ -1175,7 +1190,7 @@ static int handle_copied(BlockDriverState *bs, uint64_t guest_offset,
|
|||
|
||||
/* Cleanup */
|
||||
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
|
||||
* 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;
|
||||
int l2_index;
|
||||
uint64_t *l2_table;
|
||||
uint64_t *l2_slice;
|
||||
uint64_t entry;
|
||||
uint64_t nb_clusters;
|
||||
int ret;
|
||||
|
@ -1272,29 +1287,29 @@ static int handle_alloc(BlockDriverState *bs, uint64_t guest_offset,
|
|||
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.
|
||||
*/
|
||||
nb_clusters =
|
||||
size_to_clusters(s, offset_into_cluster(s, guest_offset) + *bytes);
|
||||
|
||||
l2_index = offset_to_l2_index(s, guest_offset);
|
||||
nb_clusters = MIN(nb_clusters, s->l2_size - l2_index);
|
||||
l2_index = offset_to_l2_slice_index(s, guest_offset);
|
||||
nb_clusters = MIN(nb_clusters, s->l2_slice_size - l2_index);
|
||||
assert(nb_clusters <= INT_MAX);
|
||||
|
||||
/* 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) {
|
||||
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 */
|
||||
if (entry & QCOW_OFLAG_COMPRESSED) {
|
||||
nb_clusters = 1;
|
||||
} 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
|
||||
|
@ -1323,7 +1338,7 @@ static int handle_alloc(BlockDriverState *bs, uint64_t guest_offset,
|
|||
* nb_clusters already to a range of COW clusters */
|
||||
preallocated_nb_clusters =
|
||||
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);
|
||||
|
||||
nb_clusters = preallocated_nb_clusters;
|
||||
|
@ -1334,7 +1349,7 @@ static int handle_alloc(BlockDriverState *bs, uint64_t guest_offset,
|
|||
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) {
|
||||
/* 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.
|
||||
* 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.
|
||||
*/
|
||||
static int discard_single_l2(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t nb_clusters, enum qcow2_discard_type type,
|
||||
bool full_discard)
|
||||
static int discard_in_l2_slice(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t nb_clusters,
|
||||
enum qcow2_discard_type type, bool full_discard)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
uint64_t *l2_table;
|
||||
uint64_t *l2_slice;
|
||||
int l2_index;
|
||||
int ret;
|
||||
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) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Limit nb_clusters to one L2 table */
|
||||
nb_clusters = MIN(nb_clusters, s->l2_size - l2_index);
|
||||
/* Limit nb_clusters to one L2 slice */
|
||||
nb_clusters = MIN(nb_clusters, s->l2_slice_size - l2_index);
|
||||
assert(nb_clusters <= INT_MAX);
|
||||
|
||||
for (i = 0; i < nb_clusters; i++) {
|
||||
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
|
||||
|
@ -1679,18 +1694,18 @@ static int discard_single_l2(BlockDriverState *bs, uint64_t offset,
|
|||
}
|
||||
|
||||
/* 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) {
|
||||
l2_table[l2_index + i] = cpu_to_be64(QCOW_OFLAG_ZERO);
|
||||
l2_slice[l2_index + i] = cpu_to_be64(QCOW_OFLAG_ZERO);
|
||||
} else {
|
||||
l2_table[l2_index + i] = cpu_to_be64(0);
|
||||
l2_slice[l2_index + i] = cpu_to_be64(0);
|
||||
}
|
||||
|
||||
/* Then decrease the refcount */
|
||||
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;
|
||||
}
|
||||
|
@ -1714,10 +1729,10 @@ int qcow2_cluster_discard(BlockDriverState *bs, uint64_t offset,
|
|||
|
||||
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) {
|
||||
cleared = discard_single_l2(bs, offset, nb_clusters, type,
|
||||
full_discard);
|
||||
cleared = discard_in_l2_slice(bs, offset, nb_clusters, type,
|
||||
full_discard);
|
||||
if (cleared < 0) {
|
||||
ret = cleared;
|
||||
goto fail;
|
||||
|
@ -1737,33 +1752,33 @@ fail:
|
|||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
static int zero_single_l2(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t nb_clusters, int flags)
|
||||
static int zero_in_l2_slice(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t nb_clusters, int flags)
|
||||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
uint64_t *l2_table;
|
||||
uint64_t *l2_slice;
|
||||
int l2_index;
|
||||
int ret;
|
||||
int i;
|
||||
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) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Limit nb_clusters to one L2 table */
|
||||
nb_clusters = MIN(nb_clusters, s->l2_size - l2_index);
|
||||
/* Limit nb_clusters to one L2 slice */
|
||||
nb_clusters = MIN(nb_clusters, s->l2_slice_size - l2_index);
|
||||
assert(nb_clusters <= INT_MAX);
|
||||
|
||||
for (i = 0; i < nb_clusters; i++) {
|
||||
uint64_t old_offset;
|
||||
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
|
||||
|
@ -1775,16 +1790,16 @@ static int zero_single_l2(BlockDriverState *bs, uint64_t offset,
|
|||
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) {
|
||||
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);
|
||||
} 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;
|
||||
}
|
||||
|
@ -1808,13 +1823,13 @@ int qcow2_cluster_zeroize(BlockDriverState *bs, uint64_t offset,
|
|||
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);
|
||||
|
||||
s->cache_discards = true;
|
||||
|
||||
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) {
|
||||
ret = cleared;
|
||||
goto fail;
|
||||
|
@ -1848,22 +1863,25 @@ static int expand_zero_clusters_in_l1(BlockDriverState *bs, uint64_t *l1_table,
|
|||
{
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
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 i, j;
|
||||
|
||||
slice_size2 = s->l2_slice_size * sizeof(uint64_t);
|
||||
n_slices = s->cluster_size / slice_size2;
|
||||
|
||||
if (!is_active_l1) {
|
||||
/* inactive L2 tables require a buffer to be stored in when loading
|
||||
* them from disk */
|
||||
l2_table = qemu_try_blockalign(bs->file->bs, s->cluster_size);
|
||||
if (l2_table == NULL) {
|
||||
l2_slice = qemu_try_blockalign(bs->file->bs, slice_size2);
|
||||
if (l2_slice == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < l1_size; i++) {
|
||||
uint64_t l2_offset = l1_table[i] & L1E_OFFSET_MASK;
|
||||
bool l2_dirty = false;
|
||||
uint64_t l2_refcount;
|
||||
|
||||
if (!l2_offset) {
|
||||
|
@ -1883,124 +1901,131 @@ static int expand_zero_clusters_in_l1(BlockDriverState *bs, uint64_t *l1_table,
|
|||
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,
|
||||
&l2_refcount);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (j = 0; j < s->l2_size; j++) {
|
||||
uint64_t l2_entry = be64_to_cpu(l2_table[j]);
|
||||
int64_t offset = l2_entry & L2E_OFFSET_MASK;
|
||||
QCow2ClusterType cluster_type = qcow2_get_cluster_type(l2_entry);
|
||||
|
||||
if (cluster_type != QCOW2_CLUSTER_ZERO_PLAIN &&
|
||||
cluster_type != QCOW2_CLUSTER_ZERO_ALLOC) {
|
||||
continue;
|
||||
for (slice = 0; slice < n_slices; slice++) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN) {
|
||||
if (!bs->backing) {
|
||||
/* not backed; therefore we can simply deallocate the
|
||||
* cluster */
|
||||
l2_table[j] = 0;
|
||||
l2_dirty = true;
|
||||
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;
|
||||
QCow2ClusterType cluster_type =
|
||||
qcow2_get_cluster_type(l2_entry);
|
||||
|
||||
if (cluster_type != QCOW2_CLUSTER_ZERO_PLAIN &&
|
||||
cluster_type != QCOW2_CLUSTER_ZERO_ALLOC) {
|
||||
continue;
|
||||
}
|
||||
|
||||
offset = qcow2_alloc_clusters(bs, s->cluster_size);
|
||||
if (offset < 0) {
|
||||
ret = offset;
|
||||
goto fail;
|
||||
}
|
||||
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN) {
|
||||
if (!bs->backing) {
|
||||
/* not backed; therefore we can simply deallocate the
|
||||
* cluster */
|
||||
l2_slice[j] = 0;
|
||||
l2_dirty = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (l2_refcount > 1) {
|
||||
/* For shared L2 tables, set the refcount accordingly (it is
|
||||
* already 1 and needs to be l2_refcount) */
|
||||
ret = qcow2_update_cluster_refcount(bs,
|
||||
offset >> s->cluster_bits,
|
||||
refcount_diff(1, l2_refcount), false,
|
||||
QCOW2_DISCARD_OTHER);
|
||||
if (ret < 0) {
|
||||
qcow2_free_clusters(bs, offset, s->cluster_size,
|
||||
QCOW2_DISCARD_OTHER);
|
||||
offset = qcow2_alloc_clusters(bs, s->cluster_size);
|
||||
if (offset < 0) {
|
||||
ret = offset;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (l2_refcount > 1) {
|
||||
/* For shared L2 tables, set the refcount accordingly
|
||||
* (it is already 1 and needs to be l2_refcount) */
|
||||
ret = qcow2_update_cluster_refcount(
|
||||
bs, offset >> s->cluster_bits,
|
||||
refcount_diff(1, l2_refcount), false,
|
||||
QCOW2_DISCARD_OTHER);
|
||||
if (ret < 0) {
|
||||
qcow2_free_clusters(bs, offset, s->cluster_size,
|
||||
QCOW2_DISCARD_OTHER);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (offset_into_cluster(s, offset)) {
|
||||
int l2_index = slice * s->l2_slice_size + j;
|
||||
qcow2_signal_corruption(
|
||||
bs, true, -1, -1,
|
||||
"Cluster allocation offset "
|
||||
"%#" PRIx64 " unaligned (L2 offset: %#"
|
||||
PRIx64 ", L2 index: %#x)", offset,
|
||||
l2_offset, l2_index);
|
||||
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN) {
|
||||
qcow2_free_clusters(bs, offset, s->cluster_size,
|
||||
QCOW2_DISCARD_ALWAYS);
|
||||
}
|
||||
ret = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = qcow2_pre_write_overlap_check(bs, 0, offset,
|
||||
s->cluster_size);
|
||||
if (ret < 0) {
|
||||
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN) {
|
||||
qcow2_free_clusters(bs, offset, s->cluster_size,
|
||||
QCOW2_DISCARD_ALWAYS);
|
||||
}
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = bdrv_pwrite_zeroes(bs->file, offset, s->cluster_size, 0);
|
||||
if (ret < 0) {
|
||||
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN) {
|
||||
qcow2_free_clusters(bs, offset, s->cluster_size,
|
||||
QCOW2_DISCARD_ALWAYS);
|
||||
}
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (l2_refcount == 1) {
|
||||
l2_slice[j] = cpu_to_be64(offset | QCOW_OFLAG_COPIED);
|
||||
} else {
|
||||
l2_slice[j] = cpu_to_be64(offset);
|
||||
}
|
||||
l2_dirty = true;
|
||||
}
|
||||
|
||||
if (offset_into_cluster(s, offset)) {
|
||||
qcow2_signal_corruption(bs, true, -1, -1,
|
||||
"Cluster allocation offset "
|
||||
"%#" PRIx64 " unaligned (L2 offset: %#"
|
||||
PRIx64 ", L2 index: %#x)", offset,
|
||||
l2_offset, j);
|
||||
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN) {
|
||||
qcow2_free_clusters(bs, offset, s->cluster_size,
|
||||
QCOW2_DISCARD_ALWAYS);
|
||||
if (is_active_l1) {
|
||||
if (l2_dirty) {
|
||||
qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_slice);
|
||||
qcow2_cache_depends_on_flush(s->l2_table_cache);
|
||||
}
|
||||
ret = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = qcow2_pre_write_overlap_check(bs, 0, offset, s->cluster_size);
|
||||
if (ret < 0) {
|
||||
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN) {
|
||||
qcow2_free_clusters(bs, offset, s->cluster_size,
|
||||
QCOW2_DISCARD_ALWAYS);
|
||||
}
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = bdrv_pwrite_zeroes(bs->file, offset, s->cluster_size, 0);
|
||||
if (ret < 0) {
|
||||
if (cluster_type == QCOW2_CLUSTER_ZERO_PLAIN) {
|
||||
qcow2_free_clusters(bs, offset, s->cluster_size,
|
||||
QCOW2_DISCARD_ALWAYS);
|
||||
}
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (l2_refcount == 1) {
|
||||
l2_table[j] = cpu_to_be64(offset | QCOW_OFLAG_COPIED);
|
||||
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||
} else {
|
||||
l2_table[j] = cpu_to_be64(offset);
|
||||
}
|
||||
l2_dirty = true;
|
||||
}
|
||||
if (l2_dirty) {
|
||||
ret = qcow2_pre_write_overlap_check(
|
||||
bs, QCOW2_OL_INACTIVE_L2 | QCOW2_OL_ACTIVE_L2,
|
||||
slice_offset, slice_size2);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (is_active_l1) {
|
||||
if (l2_dirty) {
|
||||
qcow2_cache_entry_mark_dirty(bs, s->l2_table_cache, l2_table);
|
||||
qcow2_cache_depends_on_flush(s->l2_table_cache);
|
||||
}
|
||||
qcow2_cache_put(bs, s->l2_table_cache, (void **) &l2_table);
|
||||
} else {
|
||||
if (l2_dirty) {
|
||||
ret = qcow2_pre_write_overlap_check(bs,
|
||||
QCOW2_OL_INACTIVE_L2 | QCOW2_OL_ACTIVE_L2, l2_offset,
|
||||
s->cluster_size);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = bdrv_write(bs->file, l2_offset / BDRV_SECTOR_SIZE,
|
||||
(void *)l2_table, s->cluster_sectors);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
ret = bdrv_pwrite(bs->file, slice_offset,
|
||||
l2_slice, slice_size2);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2014,11 +2039,11 @@ static int expand_zero_clusters_in_l1(BlockDriverState *bs, uint64_t *l1_table,
|
|||
ret = 0;
|
||||
|
||||
fail:
|
||||
if (l2_table) {
|
||||
if (l2_slice) {
|
||||
if (!is_active_l1) {
|
||||
qemu_vfree(l2_table);
|
||||
qemu_vfree(l2_slice);
|
||||
} else {
|
||||
qcow2_cache_put(bs, s->l2_table_cache, (void **) &l2_table);
|
||||
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||
}
|
||||
}
|
||||
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);
|
||||
*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;
|
||||
}
|
||||
|
@ -421,7 +421,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
|||
|
||||
/* Now the new refcount block needs to be written to disk */
|
||||
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);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
|
@ -449,7 +449,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
|||
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
|
||||
|
@ -501,7 +501,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
|
|||
|
||||
fail:
|
||||
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;
|
||||
}
|
||||
|
@ -623,7 +623,7 @@ int64_t qcow2_refcount_area(BlockDriverState *bs, uint64_t start_offset,
|
|||
goto fail;
|
||||
}
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
qcow2_cache_entry_mark_dirty(bs, s->refcount_block_cache,
|
||||
qcow2_cache_entry_mark_dirty(s->refcount_block_cache,
|
||||
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);
|
||||
|
@ -836,7 +836,7 @@ static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
|
|||
/* Load the refcount block and allocate it if needed */
|
||||
if (table_index != old_table_index) {
|
||||
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);
|
||||
if (ret < 0) {
|
||||
|
@ -845,8 +845,7 @@ static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
|
|||
}
|
||||
old_table_index = table_index;
|
||||
|
||||
qcow2_cache_entry_mark_dirty(bs, s->refcount_block_cache,
|
||||
refcount_block);
|
||||
qcow2_cache_entry_mark_dirty(s->refcount_block_cache, refcount_block);
|
||||
|
||||
/* we can update the count and save it */
|
||||
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) {
|
||||
void *table;
|
||||
|
||||
table = qcow2_cache_is_table_offset(bs, s->refcount_block_cache,
|
||||
table = qcow2_cache_is_table_offset(s->refcount_block_cache,
|
||||
offset);
|
||||
if (table != NULL) {
|
||||
qcow2_cache_put(bs, s->refcount_block_cache, &refcount_block);
|
||||
qcow2_cache_discard(bs, s->refcount_block_cache, table);
|
||||
qcow2_cache_put(s->refcount_block_cache, &refcount_block);
|
||||
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) {
|
||||
qcow2_cache_discard(bs, s->l2_table_cache, table);
|
||||
qcow2_cache_discard(s->l2_table_cache, table);
|
||||
}
|
||||
|
||||
if (s->discard_passthrough[type]) {
|
||||
|
@ -898,7 +897,7 @@ fail:
|
|||
|
||||
/* Write last changed block to disk */
|
||||
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)
|
||||
{
|
||||
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;
|
||||
int64_t old_entry, old_l2_offset;
|
||||
unsigned slice, slice_size2, n_slices;
|
||||
int i, j, l1_modified = 0, nb_csectors;
|
||||
int ret;
|
||||
|
||||
assert(addend >= -1 && addend <= 1);
|
||||
|
||||
l2_table = NULL;
|
||||
l2_slice = NULL;
|
||||
l1_table = NULL;
|
||||
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;
|
||||
|
||||
|
@ -1237,92 +1239,98 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
|||
goto fail;
|
||||
}
|
||||
|
||||
ret = qcow2_cache_get(bs, s->l2_table_cache, l2_offset,
|
||||
(void**) &l2_table);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
for (slice = 0; slice < n_slices; slice++) {
|
||||
ret = qcow2_cache_get(bs, s->l2_table_cache,
|
||||
l2_offset + slice * slice_size2,
|
||||
(void **) &l2_slice);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (j = 0; j < s->l2_size; j++) {
|
||||
uint64_t cluster_index;
|
||||
uint64_t offset;
|
||||
for (j = 0; j < s->l2_slice_size; j++) {
|
||||
uint64_t cluster_index;
|
||||
uint64_t offset;
|
||||
|
||||
entry = be64_to_cpu(l2_table[j]);
|
||||
old_entry = entry;
|
||||
entry &= ~QCOW_OFLAG_COPIED;
|
||||
offset = entry & L2E_OFFSET_MASK;
|
||||
entry = be64_to_cpu(l2_slice[j]);
|
||||
old_entry = entry;
|
||||
entry &= ~QCOW_OFLAG_COPIED;
|
||||
offset = entry & L2E_OFFSET_MASK;
|
||||
|
||||
switch (qcow2_get_cluster_type(entry)) {
|
||||
case QCOW2_CLUSTER_COMPRESSED:
|
||||
nb_csectors = ((entry >> s->csize_shift) &
|
||||
s->csize_mask) + 1;
|
||||
if (addend != 0) {
|
||||
ret = update_refcount(bs,
|
||||
(entry & s->cluster_offset_mask) & ~511,
|
||||
switch (qcow2_get_cluster_type(entry)) {
|
||||
case QCOW2_CLUSTER_COMPRESSED:
|
||||
nb_csectors = ((entry >> s->csize_shift) &
|
||||
s->csize_mask) + 1;
|
||||
if (addend != 0) {
|
||||
ret = update_refcount(
|
||||
bs, (entry & s->cluster_offset_mask) & ~511,
|
||||
nb_csectors * 512, abs(addend), addend < 0,
|
||||
QCOW2_DISCARD_SNAPSHOT);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
/* compressed clusters are never modified */
|
||||
refcount = 2;
|
||||
break;
|
||||
|
||||
case QCOW2_CLUSTER_NORMAL:
|
||||
case QCOW2_CLUSTER_ZERO_ALLOC:
|
||||
if (offset_into_cluster(s, offset)) {
|
||||
/* 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
|
||||
" unaligned (L2 offset: %#"
|
||||
PRIx64 ", L2 index: %#x)",
|
||||
offset, l2_offset, l2_index);
|
||||
ret = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cluster_index = offset >> s->cluster_bits;
|
||||
assert(cluster_index);
|
||||
if (addend != 0) {
|
||||
ret = qcow2_update_cluster_refcount(
|
||||
bs, cluster_index, abs(addend), addend < 0,
|
||||
QCOW2_DISCARD_SNAPSHOT);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
ret = qcow2_get_refcount(bs, cluster_index, &refcount);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
/* compressed clusters are never modified */
|
||||
refcount = 2;
|
||||
break;
|
||||
break;
|
||||
|
||||
case QCOW2_CLUSTER_NORMAL:
|
||||
case QCOW2_CLUSTER_ZERO_ALLOC:
|
||||
if (offset_into_cluster(s, offset)) {
|
||||
qcow2_signal_corruption(bs, true, -1, -1, "Cluster "
|
||||
"allocation offset %#" PRIx64
|
||||
" unaligned (L2 offset: %#"
|
||||
PRIx64 ", L2 index: %#x)",
|
||||
offset, l2_offset, j);
|
||||
ret = -EIO;
|
||||
goto fail;
|
||||
case QCOW2_CLUSTER_ZERO_PLAIN:
|
||||
case QCOW2_CLUSTER_UNALLOCATED:
|
||||
refcount = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
|
||||
cluster_index = offset >> s->cluster_bits;
|
||||
assert(cluster_index);
|
||||
if (addend != 0) {
|
||||
ret = qcow2_update_cluster_refcount(bs,
|
||||
cluster_index, abs(addend), addend < 0,
|
||||
QCOW2_DISCARD_SNAPSHOT);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
if (refcount == 1) {
|
||||
entry |= QCOW_OFLAG_COPIED;
|
||||
}
|
||||
if (entry != old_entry) {
|
||||
if (addend > 0) {
|
||||
qcow2_cache_set_dependency(bs, s->l2_table_cache,
|
||||
s->refcount_block_cache);
|
||||
}
|
||||
l2_slice[j] = cpu_to_be64(entry);
|
||||
qcow2_cache_entry_mark_dirty(s->l2_table_cache,
|
||||
l2_slice);
|
||||
}
|
||||
|
||||
ret = qcow2_get_refcount(bs, cluster_index, &refcount);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
|
||||
case QCOW2_CLUSTER_ZERO_PLAIN:
|
||||
case QCOW2_CLUSTER_UNALLOCATED:
|
||||
refcount = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
|
||||
if (refcount == 1) {
|
||||
entry |= QCOW_OFLAG_COPIED;
|
||||
}
|
||||
if (entry != old_entry) {
|
||||
if (addend > 0) {
|
||||
qcow2_cache_set_dependency(bs, s->l2_table_cache,
|
||||
s->refcount_block_cache);
|
||||
}
|
||||
l2_table[j] = cpu_to_be64(entry);
|
||||
qcow2_cache_entry_mark_dirty(bs, s->l2_table_cache,
|
||||
l2_table);
|
||||
}
|
||||
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||
}
|
||||
|
||||
qcow2_cache_put(bs, s->l2_table_cache, (void **) &l2_table);
|
||||
|
||||
if (addend != 0) {
|
||||
ret = qcow2_update_cluster_refcount(bs, l2_offset >>
|
||||
s->cluster_bits,
|
||||
|
@ -1348,8 +1356,8 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
|||
|
||||
ret = bdrv_flush(bs);
|
||||
fail:
|
||||
if (l2_table) {
|
||||
qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table);
|
||||
if (l2_slice) {
|
||||
qcow2_cache_put(s->l2_table_cache, (void **) &l2_slice);
|
||||
}
|
||||
|
||||
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_refblock_empty, allocated, errp);
|
||||
if (ret < 0) {
|
||||
qcow2_cache_put(bs, s->refcount_block_cache, &refblock);
|
||||
qcow2_cache_put(s->refcount_block_cache, &refblock);
|
||||
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) {
|
||||
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)
|
||||
+ 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;
|
||||
}
|
||||
|
||||
qcow2_cache_put(bs, s->refcount_block_cache, &refblock);
|
||||
qcow2_cache_put(s->refcount_block_cache, &refblock);
|
||||
} else {
|
||||
/* No refblock means every refcount is 0 */
|
||||
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),
|
||||
discard_block_offs,
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
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);
|
||||
if (refblock) {
|
||||
/* 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);
|
||||
|
||||
|
@ -3235,7 +3243,7 @@ int qcow2_shrink_reftable(BlockDriverState *bs)
|
|||
} else {
|
||||
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]);
|
||||
}
|
||||
|
|
|
@ -675,6 +675,11 @@ static QemuOptsList qcow2_runtime_opts = {
|
|||
.type = QEMU_OPT_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,
|
||||
.type = QEMU_OPT_SIZE,
|
||||
|
@ -706,8 +711,8 @@ static void cache_clean_timer_cb(void *opaque)
|
|||
{
|
||||
BlockDriverState *bs = opaque;
|
||||
BDRVQcow2State *s = bs->opaque;
|
||||
qcow2_cache_clean_unused(bs, s->l2_table_cache);
|
||||
qcow2_cache_clean_unused(bs, s->refcount_block_cache);
|
||||
qcow2_cache_clean_unused(s->l2_table_cache);
|
||||
qcow2_cache_clean_unused(s->refcount_block_cache);
|
||||
timer_mod(s->cache_clean_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
|
||||
(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,
|
||||
uint64_t *l2_cache_size,
|
||||
uint64_t *l2_cache_entry_size,
|
||||
uint64_t *refcount_cache_size, Error **errp)
|
||||
{
|
||||
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,
|
||||
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 (l2_cache_size_set && refcount_cache_size_set) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Qcow2Cache *l2_table_cache;
|
||||
Qcow2Cache *refcount_block_cache;
|
||||
int l2_slice_size; /* Number of entries in a slice of the L2 table */
|
||||
bool use_lazy_refcounts;
|
||||
int overlap_check;
|
||||
bool discard_passthrough[QCOW2_DISCARD_MAX];
|
||||
|
@ -823,7 +842,7 @@ static int qcow2_update_options_prepare(BlockDriverState *bs,
|
|||
QemuOpts *opts = NULL;
|
||||
const char *opt_overlap_check, *opt_overlap_check_template;
|
||||
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;
|
||||
const char *encryptfmt;
|
||||
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 */
|
||||
read_cache_sizes(bs, opts, &l2_cache_size, &refcount_cache_size,
|
||||
&local_err);
|
||||
read_cache_sizes(bs, opts, &l2_cache_size, &l2_cache_entry_size,
|
||||
&refcount_cache_size, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
l2_cache_size /= s->cluster_size;
|
||||
l2_cache_size /= l2_cache_entry_size;
|
||||
if (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->refcount_block_cache = qcow2_cache_create(bs, refcount_cache_size);
|
||||
r->l2_slice_size = l2_cache_entry_size / sizeof(uint64_t);
|
||||
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) {
|
||||
error_setg(errp, "Could not allocate metadata caches");
|
||||
ret = -ENOMEM;
|
||||
|
@ -1044,13 +1066,14 @@ static void qcow2_update_options_commit(BlockDriverState *bs,
|
|||
int i;
|
||||
|
||||
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) {
|
||||
qcow2_cache_destroy(bs, s->refcount_block_cache);
|
||||
qcow2_cache_destroy(s->refcount_block_cache);
|
||||
}
|
||||
s->l2_table_cache = r->l2_table_cache;
|
||||
s->refcount_block_cache = r->refcount_block_cache;
|
||||
s->l2_slice_size = r->l2_slice_size;
|
||||
|
||||
s->overlap_check = r->overlap_check;
|
||||
s->use_lazy_refcounts = r->use_lazy_refcounts;
|
||||
|
@ -1073,10 +1096,10 @@ static void qcow2_update_options_abort(BlockDriverState *bs,
|
|||
Qcow2ReopenState *r)
|
||||
{
|
||||
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) {
|
||||
qcow2_cache_destroy(bs, r->refcount_block_cache);
|
||||
qcow2_cache_destroy(r->refcount_block_cache);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
if (qcow2_load_autoloading_dirty_bitmaps(bs, &local_err)) {
|
||||
if (qcow2_load_dirty_bitmaps(bs, &local_err)) {
|
||||
update_header = false;
|
||||
}
|
||||
if (local_err != NULL) {
|
||||
|
@ -1514,10 +1537,10 @@ static int qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
|
|||
s->l1_table = NULL;
|
||||
cache_clean_timer_del(bs);
|
||||
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) {
|
||||
qcow2_cache_destroy(bs, s->refcount_block_cache);
|
||||
qcow2_cache_destroy(s->refcount_block_cache);
|
||||
}
|
||||
qcrypto_block_free(s->crypto);
|
||||
qapi_free_QCryptoBlockOpenOptions(s->crypto_opts);
|
||||
|
@ -2065,8 +2088,8 @@ static void qcow2_close(BlockDriverState *bs)
|
|||
}
|
||||
|
||||
cache_clean_timer_del(bs);
|
||||
qcow2_cache_destroy(bs, s->l2_table_cache);
|
||||
qcow2_cache_destroy(bs, s->refcount_block_cache);
|
||||
qcow2_cache_destroy(s->l2_table_cache);
|
||||
qcow2_cache_destroy(s->refcount_block_cache);
|
||||
|
||||
qcrypto_block_free(s->crypto);
|
||||
s->crypto = NULL;
|
||||
|
@ -3259,9 +3282,9 @@ static int qcow2_truncate(BlockDriverState *bs, int64_t offset,
|
|||
host_offset = allocation_start;
|
||||
guest_offset = old_length;
|
||||
while (nb_new_data_clusters) {
|
||||
int64_t guest_cluster = guest_offset >> s->cluster_bits;
|
||||
int64_t nb_clusters = MIN(nb_new_data_clusters,
|
||||
s->l2_size - guest_cluster % s->l2_size);
|
||||
int64_t nb_clusters = MIN(
|
||||
nb_new_data_clusters,
|
||||
s->l2_slice_size - offset_to_l2_slice_index(s, guest_offset));
|
||||
QCowL2Meta allocation = {
|
||||
.offset = guest_offset,
|
||||
.alloc_offset = host_offset,
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
#define MAX_CLUSTER_BITS 21
|
||||
|
||||
/* 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 */
|
||||
#define MIN_REFCOUNT_CACHE_SIZE 4 /* clusters */
|
||||
|
@ -100,6 +100,7 @@
|
|||
#define QCOW2_OPT_OVERLAP_INACTIVE_L2 "overlap-check.inactive-l2"
|
||||
#define QCOW2_OPT_CACHE_SIZE "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_CACHE_CLEAN_INTERVAL "cache-clean-interval"
|
||||
|
||||
|
@ -251,6 +252,7 @@ typedef struct BDRVQcow2State {
|
|||
int cluster_bits;
|
||||
int cluster_size;
|
||||
int cluster_sectors;
|
||||
int l2_slice_size;
|
||||
int l2_bits;
|
||||
int l2_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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
offset = (offset + n - 1) & ~(n - 1);
|
||||
|
@ -636,34 +648,33 @@ void qcow2_free_snapshots(BlockDriverState *bs);
|
|||
int qcow2_read_snapshots(BlockDriverState *bs);
|
||||
|
||||
/* qcow2-cache.c functions */
|
||||
Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables);
|
||||
int qcow2_cache_destroy(BlockDriverState* bs, Qcow2Cache *c);
|
||||
Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables,
|
||||
unsigned table_size);
|
||||
int qcow2_cache_destroy(Qcow2Cache *c);
|
||||
|
||||
void qcow2_cache_entry_mark_dirty(BlockDriverState *bs, Qcow2Cache *c,
|
||||
void *table);
|
||||
void qcow2_cache_entry_mark_dirty(Qcow2Cache *c, void *table);
|
||||
int qcow2_cache_flush(BlockDriverState *bs, Qcow2Cache *c);
|
||||
int qcow2_cache_write(BlockDriverState *bs, Qcow2Cache *c);
|
||||
int qcow2_cache_set_dependency(BlockDriverState *bs, Qcow2Cache *c,
|
||||
Qcow2Cache *dependency);
|
||||
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_get(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset,
|
||||
void **table);
|
||||
int qcow2_cache_get_empty(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset,
|
||||
void **table);
|
||||
void qcow2_cache_put(BlockDriverState *bs, Qcow2Cache *c, void **table);
|
||||
void *qcow2_cache_is_table_offset(BlockDriverState *bs, Qcow2Cache *c,
|
||||
uint64_t offset);
|
||||
void qcow2_cache_discard(BlockDriverState *bs, Qcow2Cache *c, void *table);
|
||||
void qcow2_cache_put(Qcow2Cache *c, void **table);
|
||||
void *qcow2_cache_is_table_offset(Qcow2Cache *c, uint64_t offset);
|
||||
void qcow2_cache_discard(Qcow2Cache *c, void *table);
|
||||
|
||||
/* qcow2-bitmap.c functions */
|
||||
int qcow2_check_bitmaps_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||
void **refcount_table,
|
||||
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);
|
||||
void qcow2_store_persistent_dirty_bitmaps(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) {
|
||||
persistent = false;
|
||||
}
|
||||
if (!has_autoload) {
|
||||
autoload = false;
|
||||
}
|
||||
|
||||
if (has_autoload && !persistent) {
|
||||
error_setg(errp, "Autoload flag must be used only for persistent "
|
||||
"bitmaps");
|
||||
return;
|
||||
if (has_autoload) {
|
||||
warn_report("Autoload option is deprecated and its value is ignored");
|
||||
}
|
||||
|
||||
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_autoload(bitmap, autoload);
|
||||
}
|
||||
|
||||
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_set_readonly(BdrvDirtyBitmap *bitmap, bool value);
|
||||
void bdrv_dirty_bitmap_set_autoload(BdrvDirtyBitmap *bitmap, bool autoload);
|
||||
void bdrv_dirty_bitmap_set_persistance(BdrvDirtyBitmap *bitmap,
|
||||
bool persistent);
|
||||
|
||||
|
|
|
@ -1593,9 +1593,9 @@
|
|||
# Qcow2 disks support persistent bitmaps. Default is false for
|
||||
# block-dirty-bitmap-add. (Since: 2.10)
|
||||
#
|
||||
# @autoload: the bitmap will be automatically loaded when the image it is stored
|
||||
# in is opened. This flag may only be specified for persistent
|
||||
# bitmaps. Default is false for block-dirty-bitmap-add. (Since: 2.10)
|
||||
# @autoload: ignored and deprecated since 2.12.
|
||||
# Currently, all dirty tracking bitmaps are loaded from Qcow2 on
|
||||
# open.
|
||||
#
|
||||
# Since: 2.4
|
||||
##
|
||||
|
@ -2521,6 +2521,11 @@
|
|||
# @l2-cache-size: the maximum size of the L2 table cache in
|
||||
# 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
|
||||
# in bytes (since 2.2)
|
||||
#
|
||||
|
@ -2542,6 +2547,7 @@
|
|||
'*overlap-check': 'Qcow2OverlapChecks',
|
||||
'*cache-size': 'int',
|
||||
'*l2-cache-size': 'int',
|
||||
'*l2-cache-entry-size': 'int',
|
||||
'*refcount-cache-size': 'int',
|
||||
'*cache-clean-interval': 'int',
|
||||
'*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
|
||||
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
|
||||
|
||||
@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
|
||||
_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 "=== Testing dirty version downgrade ==="
|
||||
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)
|
||||
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 ===
|
||||
|
||||
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" \
|
||||
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 '=== Testing valid option combinations ==='
|
||||
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' \
|
||||
| _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 '=== Testing minimal L2 cache and COW ==='
|
||||
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: 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: 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 ===
|
||||
|
||||
|
|
|
@ -83,6 +83,9 @@ $QEMU_IO \
|
|||
-c "reopen -o overlap-check.inactive-l2=off" \
|
||||
-c "reopen -o cache-size=1M" \
|
||||
-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 cache-clean-interval=5" \
|
||||
-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,refcount-cache-size=2M" \
|
||||
-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 overlap-check=constant,overlap-check.template=all" \
|
||||
-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
|
||||
refcount-cache-size may not exceed cache-size
|
||||
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
|
||||
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
|
||||
|
|
|
@ -64,7 +64,7 @@ class TestPersistentDirtyBitmap(iotests.QMPTestCase):
|
|||
|
||||
def qmpAddBitmap(self):
|
||||
self.vm.qmp('block-dirty-bitmap-add', node='drive0',
|
||||
name='bitmap0', persistent=True, autoload=True)
|
||||
name='bitmap0', persistent=True)
|
||||
|
||||
def test_persistent(self):
|
||||
self.vm = self.mkVm()
|
||||
|
|
|
@ -95,7 +95,7 @@ case $reason in
|
|||
"file": { "driver": "file", "filename": "$TEST_IMG" } } }
|
||||
{ "execute": "block-dirty-bitmap-add",
|
||||
"arguments": { "node": "drive0", "name": "bitmap0",
|
||||
"persistent": true, "autoload": true } }
|
||||
"persistent": true } }
|
||||
{ "execute": "quit" }
|
||||
EOF
|
||||
;;
|
||||
|
|
Loading…
Reference in New Issue