#include "qemu/option.h"
#include "qemu/crc32c.h"
#include "qemu/bswap.h"
+#include "qemu/error-report.h"
#include "vhdx.h"
#include "migration/blocker.h"
#include "qemu/uuid.h"
end = start + length;
QLIST_FOREACH(r, &s->regions, entries) {
if (!((start >= r->end) || (end <= r->start))) {
+ error_report("VHDX region %" PRIu64 "-%" PRIu64 " overlaps with "
+ "region %" PRIu64 "-%." PRIu64, start, end, r->start,
+ r->end);
ret = -EINVAL;
goto exit;
}
}
+static int vhdx_check_bat_entries(BlockDriverState *bs, int *errcnt)
+{
+ BDRVVHDXState *s = bs->opaque;
+ int64_t image_file_size = bdrv_getlength(bs->file->bs);
+ uint64_t payblocks = s->chunk_ratio;
+ uint64_t i;
+ int ret = 0;
+
+ if (image_file_size < 0) {
+ error_report("Could not determinate VHDX image file size.");
+ return image_file_size;
+ }
+
+ for (i = 0; i < s->bat_entries; i++) {
+ if ((s->bat[i] & VHDX_BAT_STATE_BIT_MASK) ==
+ PAYLOAD_BLOCK_FULLY_PRESENT) {
+ uint64_t offset = s->bat[i] & VHDX_BAT_FILE_OFF_MASK;
+ /*
+ * Allow that the last block exists only partially. The VHDX spec
+ * states that the image file can only grow in blocksize increments,
+ * but QEMU created images with partial last blocks in the past.
+ */
+ uint32_t block_length = MIN(s->block_size,
+ bs->total_sectors * BDRV_SECTOR_SIZE - i * s->block_size);
+ /*
+ * Check for BAT entry overflow.
+ */
+ if (offset > INT64_MAX - s->block_size) {
+ error_report("VHDX BAT entry %" PRIu64 " offset overflow.", i);
+ ret = -EINVAL;
+ if (!errcnt) {
+ break;
+ }
+ (*errcnt)++;
+ }
+ /*
+ * Check if fully allocated BAT entries do not reside after
+ * end of the image file.
+ */
+ if (offset >= image_file_size) {
+ error_report("VHDX BAT entry %" PRIu64 " start offset %" PRIu64
+ " points after end of file (%" PRIi64 "). Image"
+ " has probably been truncated.",
+ i, offset, image_file_size);
+ ret = -EINVAL;
+ if (!errcnt) {
+ break;
+ }
+ (*errcnt)++;
+ } else if (offset + block_length > image_file_size) {
+ error_report("VHDX BAT entry %" PRIu64 " end offset %" PRIu64
+ " points after end of file (%" PRIi64 "). Image"
+ " has probably been truncated.",
+ i, offset + block_length - 1, image_file_size);
+ ret = -EINVAL;
+ if (!errcnt) {
+ break;
+ }
+ (*errcnt)++;
+ }
+
+ /*
+ * verify populated BAT field file offsets against
+ * region table and log entries
+ */
+ if (payblocks--) {
+ /* payload bat entries */
+ int ret2;
+ ret2 = vhdx_region_check(s, offset, s->block_size);
+ if (ret2 < 0) {
+ ret = -EINVAL;
+ if (!errcnt) {
+ break;
+ }
+ (*errcnt)++;
+ }
+ } else {
+ payblocks = s->chunk_ratio;
+ /*
+ * Once differencing files are supported, verify sector bitmap
+ * blocks here
+ */
+ }
+ }
+ }
+
+ return ret;
+}
+
static void vhdx_close(BlockDriverState *bs)
{
BDRVVHDXState *s = bs->opaque;
goto fail;
}
- uint64_t payblocks = s->chunk_ratio;
- /* endian convert, and verify populated BAT field file offsets against
- * region table and log entries */
+ /* endian convert populated BAT field entires */
for (i = 0; i < s->bat_entries; i++) {
s->bat[i] = le64_to_cpu(s->bat[i]);
- if (payblocks--) {
- /* payload bat entries */
- if ((s->bat[i] & VHDX_BAT_STATE_BIT_MASK) ==
- PAYLOAD_BLOCK_FULLY_PRESENT) {
- ret = vhdx_region_check(s, s->bat[i] & VHDX_BAT_FILE_OFF_MASK,
- s->block_size);
- if (ret < 0) {
- goto fail;
- }
- }
- } else {
- payblocks = s->chunk_ratio;
- /* Once differencing files are supported, verify sector bitmap
- * blocks here */
+ }
+
+ if (!(flags & BDRV_O_CHECK)) {
+ ret = vhdx_check_bat_entries(bs, NULL);
+ if (ret < 0) {
+ goto fail;
}
}
return -EINVAL;
}
- return bdrv_truncate(bs->file, *new_offset + s->block_size,
+ return bdrv_truncate(bs->file, *new_offset + s->block_size, false,
PREALLOC_MODE_OFF, NULL);
}
/* Queue another write of zero buffers if the underlying file
* does not zero-fill on file extension */
- if (bdrv_has_zero_init(bs->file->bs) == 0) {
+ if (bdrv_has_zero_init_truncate(bs->file->bs) == 0) {
use_zero_buffers = true;
/* zero fill the front, if any */
if (type == VHDX_TYPE_DYNAMIC) {
/* All zeroes, so we can just extend the file - the end of the BAT
* is the furthest thing we have written yet */
- ret = blk_truncate(blk, data_file_offset, PREALLOC_MODE_OFF, errp);
+ ret = blk_truncate(blk, data_file_offset, false, PREALLOC_MODE_OFF,
+ errp);
if (ret < 0) {
goto exit;
}
} else if (type == VHDX_TYPE_FIXED) {
- ret = blk_truncate(blk, data_file_offset + image_size,
+ ret = blk_truncate(blk, data_file_offset + image_size, false,
PREALLOC_MODE_OFF, errp);
if (ret < 0) {
goto exit;
return -EIO;
}
- blk = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL);
+ blk = blk_new(bdrv_get_aio_context(bs),
+ BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL);
ret = blk_insert_bs(blk, bs, errp);
if (ret < 0) {
goto delete_and_exit;
if (s->log_replayed_on_open) {
result->corruptions_fixed++;
}
+
+ vhdx_check_bat_entries(bs, &result->corruptions);
+
return 0;
}
+static int vhdx_has_zero_init(BlockDriverState *bs)
+{
+ BDRVVHDXState *s = bs->opaque;
+ int state;
+
+ /*
+ * Check the subformat: Fixed images have all BAT entries present,
+ * dynamic images have none (right after creation). It is
+ * therefore enough to check the first BAT entry.
+ */
+ if (!s->bat_entries) {
+ return 1;
+ }
+
+ state = s->bat[0] & VHDX_BAT_STATE_BIT_MASK;
+ if (state == PAYLOAD_BLOCK_FULLY_PRESENT) {
+ /* Fixed subformat */
+ return bdrv_has_zero_init(bs->file->bs);
+ }
+
+ /* Dynamic subformat */
+ return 1;
+}
+
static QemuOptsList vhdx_create_opts = {
.name = "vhdx-create-opts",
.head = QTAILQ_HEAD_INITIALIZER(vhdx_create_opts.head),
.bdrv_co_create_opts = vhdx_co_create_opts,
.bdrv_get_info = vhdx_get_info,
.bdrv_co_check = vhdx_co_check,
- .bdrv_has_zero_init = bdrv_has_zero_init_1,
+ .bdrv_has_zero_init = vhdx_has_zero_init,
.create_opts = &vhdx_create_opts,
};