| /* | |
| * Support of Parallels Format Extension. It's a part of Parallels format | |
| * driver. | |
| * | |
| * Copyright (c) 2021 Virtuozzo International GmbH | |
| * | |
| * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| * of this software and associated documentation files (the "Software"), to deal | |
| * in the Software without restriction, including without limitation the rights | |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| * copies of the Software, and to permit persons to whom the Software is | |
| * furnished to do so, subject to the following conditions: | |
| * | |
| * The above copyright notice and this permission notice shall be included in | |
| * all copies or substantial portions of the Software. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| * THE SOFTWARE. | |
| */ | |
| #include "qemu/osdep.h" | |
| #include "qemu/error-report.h" | |
| #include "qapi/error.h" | |
| #include "block/block-io.h" | |
| #include "block/block_int.h" | |
| #include "block/dirty-bitmap.h" | |
| #include "parallels.h" | |
| #include "crypto/hash.h" | |
| #include "qemu/bswap.h" | |
| #include "qemu/host-utils.h" | |
| #include "qemu/uuid.h" | |
| #include "qemu/memalign.h" | |
| #define PARALLELS_FORMAT_EXTENSION_MAGIC 0xAB234CEF23DCEA87ULL | |
| #define PARALLELS_END_OF_FEATURES_MAGIC 0x0ULL | |
| #define PARALLELS_DIRTY_BITMAP_FEATURE_MAGIC 0x20385FAE252CB34AULL | |
| typedef struct ParallelsFormatExtensionHeader { | |
| uint64_t magic; /* PARALLELS_FORMAT_EXTENSION_MAGIC */ | |
| uint8_t check_sum[16]; | |
| } QEMU_PACKED ParallelsFormatExtensionHeader; | |
| typedef struct ParallelsFeatureHeader { | |
| uint64_t magic; | |
| uint64_t flags; | |
| uint32_t data_size; | |
| uint32_t _unused; | |
| } QEMU_PACKED ParallelsFeatureHeader; | |
| typedef struct ParallelsDirtyBitmapFeature { | |
| uint64_t size; | |
| uint8_t id[16]; | |
| uint32_t granularity; | |
| uint32_t l1_size; | |
| /* L1 table follows */ | |
| } QEMU_PACKED ParallelsDirtyBitmapFeature; | |
| /* Given L1 table read bitmap data from the image and populate @bitmap */ | |
| static int GRAPH_RDLOCK | |
| parallels_load_bitmap_data(BlockDriverState *bs, const uint64_t *l1_table, | |
| uint32_t l1_size, BdrvDirtyBitmap *bitmap, | |
| Error **errp) | |
| { | |
| BDRVParallelsState *s = bs->opaque; | |
| int ret = 0; | |
| uint64_t offset, limit; | |
| uint64_t bm_size = bdrv_dirty_bitmap_size(bitmap); | |
| int64_t file_size, data_start_off; | |
| uint8_t *buf = NULL; | |
| uint64_t i; | |
| file_size = bdrv_getlength(bs->file->bs); | |
| if (file_size < 0) { | |
| error_setg_errno(errp, -file_size, "Failed to get image file length"); | |
| return file_size; | |
| } | |
| data_start_off = s->data_start << BDRV_SECTOR_BITS; | |
| buf = qemu_try_blockalign(bs->file->bs, s->cluster_size); | |
| if (!buf) { | |
| error_setg(errp, "Failed to allocate a bitmap data cluster"); | |
| return -ENOMEM; | |
| } | |
| limit = bdrv_dirty_bitmap_serialization_coverage(s->cluster_size, bitmap); | |
| for (i = 0, offset = 0; i < l1_size; ++i, offset += limit) { | |
| uint64_t count, entry; | |
| if (offset >= bm_size) { | |
| error_setg(errp, "Bitmap L1 table covers more than the bitmap " | |
| "size %" PRIu64, bm_size); | |
| ret = -EINVAL; | |
| goto finish; | |
| } | |
| count = MIN(bm_size - offset, limit); | |
| entry = l1_table[i]; | |
| if (entry == 0) { | |
| /* No need to deserialize zeros because @bitmap is cleared. */ | |
| continue; | |
| } | |
| if (entry == 1) { | |
| bdrv_dirty_bitmap_deserialize_ones(bitmap, offset, count, false); | |
| } else { | |
| int64_t host_off; | |
| if (entry > INT64_MAX / BDRV_SECTOR_SIZE) { | |
| error_setg(errp, "Bitmap L1 entry %" PRIu64 " is out of range", | |
| i); | |
| ret = -EINVAL; | |
| goto finish; | |
| } | |
| host_off = entry * BDRV_SECTOR_SIZE; | |
| if (host_off < data_start_off) { | |
| error_setg(errp, "Bitmap L1 entry %" PRIu64 " points before " | |
| "the data area of the image", i); | |
| ret = -EINVAL; | |
| goto finish; | |
| } | |
| if (host_off > file_size - (int64_t)s->cluster_size) { | |
| error_setg(errp, "Bitmap L1 entry %" PRIu64 " points outside " | |
| "the image file", i); | |
| ret = -EINVAL; | |
| goto finish; | |
| } | |
| ret = bdrv_pread(bs->file, host_off, s->cluster_size, buf, 0); | |
| if (ret < 0) { | |
| error_setg_errno(errp, -ret, | |
| "Failed to read bitmap data cluster"); | |
| goto finish; | |
| } | |
| bdrv_dirty_bitmap_deserialize_part(bitmap, buf, offset, count, | |
| false); | |
| s->ext_end = MAX(s->ext_end, host_off + s->cluster_size); | |
| } | |
| } | |
| ret = 0; | |
| bdrv_dirty_bitmap_deserialize_finish(bitmap); | |
| finish: | |
| qemu_vfree(buf); | |
| return ret; | |
| } | |
| /* | |
| * @data buffer (of @data_size size) is the Dirty bitmaps feature which | |
| * consists of ParallelsDirtyBitmapFeature followed by L1 table. | |
| */ | |
| static BdrvDirtyBitmap * GRAPH_RDLOCK | |
| parallels_load_bitmap(BlockDriverState *bs, uint8_t *data, size_t data_size, | |
| Error **errp) | |
| { | |
| BDRVParallelsState *s = bs->opaque; | |
| int ret; | |
| ParallelsDirtyBitmapFeature bf; | |
| g_autofree uint64_t *l1_table = NULL; | |
| BdrvDirtyBitmap *bitmap; | |
| QemuUUID uuid; | |
| char uuidstr[UUID_STR_LEN]; | |
| uint64_t bm_size, tab_size, granularity; | |
| int i; | |
| if (data_size < sizeof(bf)) { | |
| error_setg(errp, "Too small Bitmap Feature area in Parallels Format " | |
| "Extension: %zu bytes, expected at least %zu bytes", | |
| data_size, sizeof(bf)); | |
| return NULL; | |
| } | |
| memcpy(&bf, data, sizeof(bf)); | |
| bf.size = le64_to_cpu(bf.size); | |
| bf.granularity = le32_to_cpu(bf.granularity); | |
| bf.l1_size = le32_to_cpu(bf.l1_size); | |
| data += sizeof(bf); | |
| data_size -= sizeof(bf); | |
| if (bf.size != bs->total_sectors) { | |
| error_setg(errp, "Bitmap size (in sectors) %" PRId64 " differs from " | |
| "disk size in sectors %" PRId64, bf.size, bs->total_sectors); | |
| return NULL; | |
| } | |
| /* bdrv_create_dirty_bitmap() asserts on an unusable granularity */ | |
| granularity = (uint64_t)bf.granularity << BDRV_SECTOR_BITS; | |
| if (granularity < BDRV_SECTOR_SIZE || granularity > UINT32_MAX || | |
| !is_power_of_2(granularity)) { | |
| error_setg(errp, "Invalid bitmap granularity %" PRIu64 ", expected a " | |
| "power of two of at least %" PRIu64 " bytes", granularity, | |
| (uint64_t)BDRV_SECTOR_SIZE); | |
| return NULL; | |
| } | |
| if (bf.l1_size * sizeof(uint64_t) > data_size) { | |
| error_setg(errp, "Bitmaps feature corrupted: l1 table exceeds " | |
| "extension data_size"); | |
| return NULL; | |
| } | |
| memcpy(&uuid, bf.id, sizeof(uuid)); | |
| qemu_uuid_unparse(&uuid, uuidstr); | |
| bitmap = bdrv_create_dirty_bitmap(bs, granularity, uuidstr, errp); | |
| if (!bitmap) { | |
| return NULL; | |
| } | |
| bm_size = bdrv_dirty_bitmap_size(bitmap); | |
| tab_size = DIV_ROUND_UP( | |
| bdrv_dirty_bitmap_serialization_size(bitmap, 0, bm_size), | |
| s->cluster_size); | |
| if (tab_size != bf.l1_size) { | |
| error_setg(errp, "Bitmap table size %" PRIu32 " does not correspond " | |
| "to bitmap size and cluster size. Expected %" PRIu64, | |
| bf.l1_size, tab_size); | |
| goto fail; | |
| } | |
| if (bf.l1_size != 0) { | |
| l1_table = g_try_new(uint64_t, bf.l1_size); | |
| if (!l1_table) { | |
| error_setg(errp, "Failed to allocate the bitmap L1 table " | |
| "(%" PRIu32 " entries)", bf.l1_size); | |
| goto fail; | |
| } | |
| for (i = 0; i < bf.l1_size; i++, data += sizeof(uint64_t)) { | |
| l1_table[i] = ldq_le_p(data); | |
| } | |
| ret = parallels_load_bitmap_data(bs, l1_table, bf.l1_size, bitmap, | |
| errp); | |
| if (ret < 0) { | |
| goto fail; | |
| } | |
| } | |
| if (!(bs->open_flags & BDRV_O_RDWR)) { | |
| bdrv_dirty_bitmap_set_readonly(bitmap, true); | |
| } | |
| return bitmap; | |
| fail: | |
| bdrv_release_dirty_bitmap(bitmap); | |
| return NULL; | |
| } | |
| static int GRAPH_RDLOCK | |
| parallels_parse_format_extension(BlockDriverState *bs, uint8_t *ext_cluster, | |
| Error **errp) | |
| { | |
| BDRVParallelsState *s = bs->opaque; | |
| int ret = -EINVAL; | |
| int remaining = s->cluster_size; | |
| uint8_t *pos = ext_cluster; | |
| ParallelsFormatExtensionHeader eh; | |
| g_autofree uint8_t *hash = NULL; | |
| size_t hash_len = 0; | |
| GSList *bitmaps = NULL, *el; | |
| memcpy(&eh, pos, sizeof(eh)); | |
| eh.magic = le64_to_cpu(eh.magic); | |
| pos += sizeof(eh); | |
| remaining -= sizeof(eh); | |
| if (eh.magic != PARALLELS_FORMAT_EXTENSION_MAGIC) { | |
| error_setg(errp, "Wrong parallels Format Extension magic: 0x%" PRIx64 | |
| ", expected: 0x%llx", eh.magic, | |
| PARALLELS_FORMAT_EXTENSION_MAGIC); | |
| ret = -ENOENT; | |
| goto fail; | |
| } | |
| if (qcrypto_hash_bytes(QCRYPTO_HASH_ALGO_MD5, (char *)pos, remaining, | |
| &hash, &hash_len, errp) < 0) { | |
| goto fail; | |
| } | |
| if (hash_len != sizeof(eh.check_sum) || | |
| memcmp(hash, eh.check_sum, sizeof(eh.check_sum)) != 0) { | |
| error_setg(errp, "Wrong checksum in Format Extension header. Format " | |
| "extension is corrupted."); | |
| ret = -ENOENT; | |
| goto fail; | |
| } | |
| while (true) { | |
| ParallelsFeatureHeader fh; | |
| BdrvDirtyBitmap *bitmap; | |
| uint64_t data_size; | |
| if (remaining < sizeof(fh)) { | |
| error_setg(errp, "Can not read feature header, as remaining bytes " | |
| "(%d) in Format Extension is less than Feature header " | |
| "size (%zu)", remaining, sizeof(fh)); | |
| goto fail; | |
| } | |
| memcpy(&fh, pos, sizeof(fh)); | |
| pos += sizeof(fh); | |
| remaining -= sizeof(fh); | |
| fh.magic = le64_to_cpu(fh.magic); | |
| fh.flags = le64_to_cpu(fh.flags); | |
| fh.data_size = le32_to_cpu(fh.data_size); | |
| if (fh.flags) { | |
| error_setg(errp, "Flags for extension feature are unsupported"); | |
| goto fail; | |
| } | |
| data_size = QEMU_ALIGN_UP((uint64_t)fh.data_size, 8); | |
| if (data_size > remaining) { | |
| error_setg(errp, "Feature data_size exceedes Format Extension " | |
| "cluster"); | |
| goto fail; | |
| } | |
| switch (fh.magic) { | |
| case PARALLELS_END_OF_FEATURES_MAGIC: | |
| g_slist_free(bitmaps); | |
| return 0; | |
| case PARALLELS_DIRTY_BITMAP_FEATURE_MAGIC: | |
| bitmap = parallels_load_bitmap(bs, pos, fh.data_size, errp); | |
| if (!bitmap) { | |
| goto fail; | |
| } | |
| bdrv_dirty_bitmap_set_persistence(bitmap, true); | |
| if (s->header_unclean) { | |
| bdrv_dirty_bitmap_set_inconsistent(bitmap); | |
| } | |
| bitmaps = g_slist_append(bitmaps, bitmap); | |
| break; | |
| default: | |
| error_setg(errp, "Unknown feature: 0x%" PRIx64, fh.magic); | |
| goto fail; | |
| } | |
| pos += data_size; | |
| remaining -= data_size; | |
| } | |
| fail: | |
| for (el = bitmaps; el; el = el->next) { | |
| bdrv_release_dirty_bitmap(el->data); | |
| } | |
| g_slist_free(bitmaps); | |
| return ret; | |
| } | |
| int parallels_read_format_extension(BlockDriverState *bs, | |
| int64_t ext_off, Error **errp) | |
| { | |
| BDRVParallelsState *s = bs->opaque; | |
| int ret; | |
| uint8_t *ext_cluster; | |
| assert(ext_off > 0); | |
| s->ext_end = ext_off + s->cluster_size; | |
| ext_cluster = qemu_try_blockalign(bs->file->bs, s->cluster_size); | |
| if (!ext_cluster) { | |
| error_setg(errp, "Failed to allocate the Format Extension cluster"); | |
| return -ENOMEM; | |
| } | |
| ret = bdrv_pread(bs->file, ext_off, s->cluster_size, ext_cluster, 0); | |
| if (ret < 0) { | |
| error_setg_errno(errp, -ret, "Failed to read Format Extension cluster"); | |
| goto out; | |
| } | |
| ret = parallels_parse_format_extension(bs, ext_cluster, errp); | |
| out: | |
| qemu_vfree(ext_cluster); | |
| return ret; | |
| } | |
| static uint64_t parallels_bitmap_l1_size(uint32_t cluster_size, uint64_t bytes, | |
| uint32_t granularity) | |
| { | |
| uint64_t granules = DIV_ROUND_UP(bytes, granularity); | |
| return DIV_ROUND_UP(granules, (uint64_t)cluster_size * 8); | |
| } | |
| static uint64_t parallels_bitmap_feature_size(uint64_t l1_size) | |
| { | |
| return l1_size * sizeof(uint64_t) + sizeof(ParallelsFeatureHeader) + | |
| sizeof(ParallelsDirtyBitmapFeature); | |
| } | |
| static int GRAPH_RDLOCK parallels_save_bitmap(BlockDriverState *bs, | |
| BdrvDirtyBitmap *bitmap, | |
| uint8_t **buf, int *buf_size, | |
| GArray *clusters, Error **errp) | |
| { | |
| BDRVParallelsState *s = bs->opaque; | |
| ParallelsFeatureHeader *fh; | |
| ParallelsDirtyBitmapFeature *bh; | |
| uint64_t *l1_table, l1_size, granularity, limit, idx; | |
| int64_t bm_size, ser_size, offset, buf_used; | |
| int64_t alloc_size = 1; | |
| const char *name; | |
| uint8_t *bm_buf; | |
| QemuUUID uuid; | |
| int ret = 0; | |
| if (!bdrv_dirty_bitmap_get_persistence(bitmap)) { | |
| return 0; | |
| } | |
| /* The format has no way to mark a stored bitmap unusable */ | |
| if (bdrv_dirty_bitmap_inconsistent(bitmap)) { | |
| warn_report("Dropping inconsistent bitmap %s", | |
| bdrv_dirty_bitmap_name(bitmap)); | |
| return 0; | |
| } | |
| name = bdrv_dirty_bitmap_name(bitmap); | |
| ret = qemu_uuid_parse(name, &uuid); | |
| if (ret < 0) { | |
| error_setg(errp, "Can't save dirty bitmap: ID parsing error: '%s'", | |
| name); | |
| return ret; | |
| } | |
| bm_size = bdrv_dirty_bitmap_size(bitmap); | |
| granularity = bdrv_dirty_bitmap_granularity(bitmap); | |
| limit = bdrv_dirty_bitmap_serialization_coverage(s->cluster_size, bitmap); | |
| ser_size = bdrv_dirty_bitmap_serialization_size(bitmap, 0, bm_size); | |
| l1_size = DIV_ROUND_UP(ser_size, s->cluster_size); | |
| /* The end of features marker has to fit behind the feature as well */ | |
| buf_used = parallels_bitmap_feature_size(l1_size); | |
| if (buf_used + (int64_t)sizeof(*fh) > *buf_size) { | |
| error_setg(errp, "Can't save dirty bitmap %s: it needs %" PRId64 | |
| " bytes of the Format Extension cluster, %d bytes are left", | |
| name, buf_used, *buf_size); | |
| return -ENOSPC; | |
| } | |
| fh = (ParallelsFeatureHeader *)*buf; | |
| bh = (ParallelsDirtyBitmapFeature *)(*buf + sizeof(*fh)); | |
| l1_table = (uint64_t *)((uint8_t *)bh + sizeof(*bh)); | |
| fh->magic = cpu_to_le64(PARALLELS_DIRTY_BITMAP_FEATURE_MAGIC); | |
| fh->data_size = cpu_to_le32(l1_size * 8 + sizeof(*bh)); | |
| bh->l1_size = cpu_to_le32(l1_size); | |
| bh->size = cpu_to_le64(bm_size >> BDRV_SECTOR_BITS); | |
| bh->granularity = cpu_to_le32(granularity >> BDRV_SECTOR_BITS); | |
| memcpy(bh->id, &uuid, sizeof(uuid)); | |
| bm_buf = qemu_try_blockalign(bs->file->bs, s->cluster_size); | |
| if (!bm_buf) { | |
| error_setg(errp, "Can't save dirty bitmap %s: allocation error", name); | |
| ret = -ENOMEM; | |
| goto fail; | |
| } | |
| offset = 0; | |
| while ((offset = bdrv_dirty_bitmap_next_dirty(bitmap, offset, | |
| bm_size)) >= 0) { | |
| int64_t cluster_off, end, write_size, first_zero; | |
| idx = offset / limit; | |
| offset = QEMU_ALIGN_DOWN(offset, limit); | |
| end = MIN(bm_size, offset + limit); | |
| first_zero = bdrv_dirty_bitmap_next_zero(bitmap, offset, end - offset); | |
| if (first_zero < 0) { | |
| l1_table[idx] = cpu_to_le64(1); | |
| offset = end; | |
| continue; | |
| } | |
| write_size = bdrv_dirty_bitmap_serialization_size(bitmap, offset, | |
| end - offset); | |
| assert(write_size <= s->cluster_size); | |
| bdrv_dirty_bitmap_serialize_part(bitmap, bm_buf, offset, end - offset); | |
| if (write_size < s->cluster_size) { | |
| memset(bm_buf + write_size, 0, s->cluster_size - write_size); | |
| } | |
| cluster_off = parallels_allocate_host_clusters(bs, &alloc_size); | |
| if (cluster_off <= 0) { | |
| ret = cluster_off < 0 ? cluster_off : -ENOSPC; | |
| error_setg_errno(errp, -ret, "Can't save dirty bitmap %s: cluster " | |
| "allocation error", name); | |
| goto fail; | |
| } | |
| ret = bdrv_pwrite(bs->file, cluster_off, s->cluster_size, bm_buf, 0); | |
| if (ret < 0) { | |
| parallels_mark_unused(bs, s->used_bmap, s->used_bmap_size, | |
| cluster_off, 1); | |
| error_setg_errno(errp, -ret, "Can't save dirty bitmap %s: IO error", | |
| name); | |
| goto fail; | |
| } | |
| l1_table[idx] = cpu_to_le64(cluster_off >> BDRV_SECTOR_BITS); | |
| s->ext_end = MAX(s->ext_end, cluster_off + s->cluster_size); | |
| g_array_append_val(clusters, cluster_off); | |
| offset = end; | |
| } | |
| *buf_size -= buf_used; | |
| *buf += buf_used; | |
| qemu_vfree(bm_buf); | |
| return 0; | |
| fail: | |
| /* Hand the clusters of the half written bitmap back to the allocator */ | |
| for (idx = 0; idx < l1_size; idx++) { | |
| uint64_t entry = le64_to_cpu(l1_table[idx]); | |
| if (entry > 1) { | |
| parallels_mark_unused(bs, s->used_bmap, s->used_bmap_size, | |
| entry << BDRV_SECTOR_BITS, 1); | |
| } | |
| } | |
| /* Leave nothing behind a reader could take for a complete feature */ | |
| memset(fh, 0, buf_used); | |
| qemu_vfree(bm_buf); | |
| return ret; | |
| } | |
| void GRAPH_RDLOCK | |
| parallels_store_persistent_dirty_bitmaps(BlockDriverState *bs, Error **errp) | |
| { | |
| BDRVParallelsState *s = bs->opaque; | |
| BdrvDirtyBitmap *bitmap; | |
| ParallelsFormatExtensionHeader *eh; | |
| int remaining = s->cluster_size - sizeof(*eh); | |
| uint8_t *buf, *pos; | |
| int64_t header_off, alloc_size = 1; | |
| g_autoptr(GArray) clusters = g_array_new(false, false, sizeof(int64_t)); | |
| g_autofree uint8_t *hash = NULL; | |
| Error *bitmap_err = NULL; | |
| size_t hash_len = 0; | |
| int ret; | |
| guint i; | |
| s->header->ext_off = 0; | |
| s->ext_end = 0; | |
| if (!bdrv_has_named_bitmaps(bs)) { | |
| return; | |
| } | |
| buf = qemu_try_blockalign0(bs->file->bs, s->cluster_size); | |
| if (!buf) { | |
| error_setg(errp, "Can't save dirty bitmaps: allocation error"); | |
| return; | |
| } | |
| eh = (ParallelsFormatExtensionHeader *)buf; | |
| pos = buf + sizeof(*eh); | |
| eh->magic = cpu_to_le64(PARALLELS_FORMAT_EXTENSION_MAGIC); | |
| FOR_EACH_DIRTY_BITMAP(bs, bitmap) { | |
| Error *local_err = NULL; | |
| /* | |
| * The extension is written as a whole, so a bitmap which does not | |
| * make it must not take with it the ones which did. | |
| */ | |
| if (parallels_save_bitmap(bs, bitmap, &pos, &remaining, clusters, | |
| &local_err) < 0) { | |
| error_propagate(&bitmap_err, local_err); | |
| } | |
| } | |
| if (pos == buf + sizeof(*eh)) { | |
| /* Not a single bitmap made it, so there is nothing to point at */ | |
| goto end; | |
| } | |
| header_off = parallels_allocate_host_clusters(bs, &alloc_size); | |
| if (header_off <= 0) { | |
| ret = header_off < 0 ? header_off : -ENOSPC; | |
| error_setg_errno(errp, -ret, | |
| "Can't save dirty bitmaps: cluster allocation error"); | |
| goto end; | |
| } | |
| g_array_append_val(clusters, header_off); | |
| ret = qcrypto_hash_bytes(QCRYPTO_HASH_ALGO_MD5, | |
| (const char *)(buf + sizeof(*eh)), | |
| s->cluster_size - sizeof(*eh), | |
| &hash, &hash_len, NULL); | |
| if (ret < 0 || hash_len != sizeof(eh->check_sum)) { | |
| error_setg(errp, "Can't save dirty bitmaps: hash error"); | |
| goto end; | |
| } | |
| memcpy(eh->check_sum, hash, hash_len); | |
| ret = bdrv_pwrite(bs->file, header_off, s->cluster_size, buf, 0); | |
| if (ret < 0) { | |
| error_setg_errno(errp, -ret, "Can't save dirty bitmaps: IO error"); | |
| goto end; | |
| } | |
| s->header->ext_off = cpu_to_le64(header_off / BDRV_SECTOR_SIZE); | |
| s->ext_end = MAX(s->ext_end, header_off + s->cluster_size); | |
| end: | |
| for (i = 0; i < clusters->len; i++) { | |
| parallels_mark_unused(bs, s->used_bmap, s->used_bmap_size, | |
| g_array_index(clusters, int64_t, i), 1); | |
| } | |
| /* A bitmap which was dropped only matters if the rest went through */ | |
| error_propagate(errp, bitmap_err); | |
| qemu_vfree(buf); | |
| } | |
| bool coroutine_fn parallels_co_can_store_new_dirty_bitmap(BlockDriverState *bs, | |
| const char *name, | |
| uint32_t granularity, | |
| Error **errp) | |
| { | |
| BDRVParallelsState *s = bs->opaque; | |
| BdrvDirtyBitmap *bitmap; | |
| uint64_t needed, available; | |
| QemuUUID uuid; | |
| if (bdrv_find_dirty_bitmap(bs, name)) { | |
| error_setg(errp, "Bitmap already exists: %s", name); | |
| return false; | |
| } | |
| if (qemu_uuid_parse(name, &uuid) < 0) { | |
| error_setg(errp, "Bitmap name must be a UUID to be stored in a " | |
| "parallels image: %s", name); | |
| return false; | |
| } | |
| /* | |
| * One L1 entry covers a cluster worth of serialized bits, and every | |
| * bitmap of the image shares the Format Extension cluster with the | |
| * feature headers and the end of features marker. | |
| */ | |
| needed = parallels_bitmap_feature_size( | |
| parallels_bitmap_l1_size(s->cluster_size, | |
| bs->total_sectors << BDRV_SECTOR_BITS, | |
| granularity)); | |
| FOR_EACH_DIRTY_BITMAP(bs, bitmap) { | |
| if (!bdrv_dirty_bitmap_get_persistence(bitmap)) { | |
| continue; | |
| } | |
| needed += parallels_bitmap_feature_size( | |
| parallels_bitmap_l1_size(s->cluster_size, | |
| bdrv_dirty_bitmap_size(bitmap), | |
| bdrv_dirty_bitmap_granularity(bitmap))); | |
| } | |
| needed += sizeof(ParallelsFeatureHeader); | |
| available = s->cluster_size - sizeof(ParallelsFormatExtensionHeader); | |
| if (needed > available) { | |
| error_setg(errp, "Bitmap %s with granularity %" PRIu32 " does not fit " | |
| "into the Format Extension cluster: every bitmap of the " | |
| "image would need %" PRIu64 " bytes of it, %" PRIu64 " are " | |
| "available", name, granularity, needed, available); | |
| return false; | |
| } | |
| return true; | |
| } | |
| int coroutine_fn | |
| parallels_co_remove_persistent_dirty_bitmap(BlockDriverState *bs, | |
| const char *name, Error **errp) | |
| { | |
| BDRVParallelsState *s = bs->opaque; | |
| BdrvDirtyBitmap *bitmap; | |
| Error *err = NULL; | |
| int ret; | |
| if (bdrv_is_read_only(bs) || (bdrv_get_flags(bs) & BDRV_O_INACTIVE)) { | |
| error_setg(errp, "Cannot remove persistent bitmap '%s': no write " | |
| "access to node '%s'", name, bdrv_get_node_name(bs)); | |
| return -EACCES; | |
| } | |
| bitmap = bdrv_find_dirty_bitmap(bs, name); | |
| if (bitmap == NULL || !bdrv_dirty_bitmap_get_persistence(bitmap)) { | |
| return 0; | |
| } | |
| /* The extension is written as a whole, so drop it from what goes in */ | |
| bdrv_dirty_bitmap_set_persistence(bitmap, false); | |
| ret = 0; | |
| WITH_QEMU_LOCK_GUARD(&s->lock) { | |
| parallels_store_persistent_dirty_bitmaps(bs, &err); | |
| if (err != NULL) { | |
| error_propagate(errp, err); | |
| ret = -EIO; | |
| break; | |
| } | |
| ret = parallels_update_header(bs); | |
| if (ret < 0) { | |
| error_setg_errno(errp, -ret, "Failed to update the image header"); | |
| break; | |
| } | |
| } | |
| if (ret < 0) { | |
| /* Nothing was removed, so the bitmap is as persistent as it was */ | |
| bdrv_dirty_bitmap_set_persistence(bitmap, true); | |
| } | |
| return ret; | |
| } | |
| void parallels_get_bitmap_info_list(BlockDriverState *bs, | |
| ParallelsBitmapInfoList **info_list) | |
| { | |
| BdrvDirtyBitmap *bitmap; | |
| ParallelsBitmapInfoList **tail = info_list; | |
| *info_list = NULL; | |
| FOR_EACH_DIRTY_BITMAP(bs, bitmap) { | |
| ParallelsBitmapInfo *info; | |
| if (!bdrv_dirty_bitmap_get_persistence(bitmap)) { | |
| continue; | |
| } | |
| info = g_new0(ParallelsBitmapInfo, 1); | |
| info->name = g_strdup(bdrv_dirty_bitmap_name(bitmap)); | |
| info->granularity = bdrv_dirty_bitmap_granularity(bitmap); | |
| if (bdrv_dirty_bitmap_inconsistent(bitmap)) { | |
| info->has_inconsistent = true; | |
| info->inconsistent = true; | |
| } | |
| QAPI_LIST_APPEND(tail, info); | |
| } | |
| } |