static int expand_zero_clusters_in_l1(BlockDriverState *bs, uint64_t *l1_table, int l1_size, uint8_t **expanded_clusters, uint64_t *nb_clusters) { BDRVQcowState *s = bs->opaque; bool is_active_l1 = (l1_table == s->l1_table); uint64_t *l2_table = NULL; int ret; int i, j; if (!is_active_l1) { /* inactive L2 tables require a buffer to be stored in when loading * them from disk */ l2_table = qemu_blockalign(bs, s->cluster_size); } for (i = 0; i < l1_size; i++) { uint64_t l2_offset = l1_table[i] & L1E_OFFSET_MASK; bool l2_dirty = false; if (!l2_offset) { /* unallocated */ continue; } 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; } 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, cluster_index; int cluster_type = qcow2_get_cluster_type(l2_entry); bool preallocated = offset != 0; if (cluster_type == QCOW2_CLUSTER_NORMAL) { cluster_index = offset >> s->cluster_bits; assert((cluster_index >= 0) && (cluster_index < *nb_clusters)); if ((*expanded_clusters)[cluster_index / 8] & (1 << (cluster_index % 8))) { /* Probably a shared L2 table; this cluster was a zero * cluster which has been expanded, its refcount * therefore most likely requires an update. */ ret = qcow2_update_cluster_refcount(bs, cluster_index, 1, QCOW2_DISCARD_NEVER); if (ret < 0) { goto fail; } /* Since we just increased the refcount, the COPIED flag may * no longer be set. */ l2_table[j] = cpu_to_be64(l2_entry & ~QCOW_OFLAG_COPIED); l2_dirty = true; } continue; } else if (qcow2_get_cluster_type(l2_entry) != QCOW2_CLUSTER_ZERO) { continue; } if (!preallocated) { if (!bs->backing_hd) { /* not backed; therefore we can simply deallocate the * cluster */ l2_table[j] = 0; l2_dirty = true; continue; } offset = qcow2_alloc_clusters(bs, s->cluster_size); if (offset < 0) { ret = offset; goto fail; } } ret = qcow2_pre_write_overlap_check(bs, QCOW2_OL_DEFAULT, offset, s->cluster_size); if (ret < 0) { if (!preallocated) { qcow2_free_clusters(bs, offset, s->cluster_size, QCOW2_DISCARD_ALWAYS); } goto fail; } ret = bdrv_write_zeroes(bs->file, offset / BDRV_SECTOR_SIZE, s->cluster_sectors); if (ret < 0) { if (!preallocated) { qcow2_free_clusters(bs, offset, s->cluster_size, QCOW2_DISCARD_ALWAYS); } goto fail; } l2_table[j] = cpu_to_be64(offset | QCOW_OFLAG_COPIED); l2_dirty = true; cluster_index = offset >> s->cluster_bits; if (cluster_index >= *nb_clusters) { uint64_t old_bitmap_size = (*nb_clusters + 7) / 8; uint64_t new_bitmap_size; /* The offset may lie beyond the old end of the underlying image * file for growable files only */ assert(bs->file->growable); *nb_clusters = size_to_clusters(s, bs->file->total_sectors * BDRV_SECTOR_SIZE); new_bitmap_size = (*nb_clusters + 7) / 8; *expanded_clusters = g_realloc(*expanded_clusters, new_bitmap_size); /* clear the newly allocated space */ memset(&(*expanded_clusters)[old_bitmap_size], 0, new_bitmap_size - old_bitmap_size); } assert((cluster_index >= 0) && (cluster_index < *nb_clusters)); (*expanded_clusters)[cluster_index / 8] |= 1 << (cluster_index % 8); } if (is_active_l1) { if (l2_dirty) { qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_table); qcow2_cache_depends_on_flush(s->l2_table_cache); } ret = qcow2_cache_put(bs, s->l2_table_cache, (void **)&l2_table); if (ret < 0) { l2_table = NULL; goto fail; } } else { if (l2_dirty) { ret = qcow2_pre_write_overlap_check(bs, QCOW2_OL_DEFAULT & ~(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 = 0; fail: if (l2_table) { if (!is_active_l1) { qemu_vfree(l2_table); } else { if (ret < 0) { qcow2_cache_put(bs, s->l2_table_cache, (void **)&l2_table); } else { ret = qcow2_cache_put(bs, s->l2_table_cache, (void **)&l2_table); } } } return ret; }