* [PATCH v3 0/3] block/vmdk: fixes for stream optimized disks
@ 2026-10-16 12:37 Sanne Raymaekers
2026-10-16 12:37 ` [PATCH v3 1/3] block/vmdk: align stream optimized data grains on 512 byte chunks Sanne Raymaekers
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Sanne Raymaekers @ 2026-10-16 12:37 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-block, fam, kwolf, hreitz, Sanne Raymaekers
The stream optimized disks produced by qemu-img did not quite adhere to
the specification. This could trip up other software consuming these
disks. The goal of these patches is to get the VMDKs produced by QEMU to
better match the specification, the Virtual Disk Format 5.0 Technical
Note.
For instance, when packaging the VMDK up in an OVA archive (a ustar
archive containing an OVF, a manifest and a VMDK), errors occur when
importing that OVA into VMWare Workstation. Specifically the digest in
the manifest does not match the digest of the VMDK, because Workstation
reads (and parses) the disk contents and then makes a digest, rather
than making a digest of the whole VMDK file. Extra null bytes at the
very end will trip it up, as they are ignored in the digest made by
Workstation, but are included in the digest produced by something like
shasum.
Note that stream optimized disks, while not adhering to the
specification fully, are still useable by VMWare tooling, with some
workarounds where needed. For instance, VSphere does not have the same
issue as Workstation.
V3 cleans up some formatting and makes the commit messages clearer.
Sanne Raymaekers (3):
block/vmdk: align stream optimized data grains on 512 byte chunks
block/vmdk: skip RGD for stream optimized disks
block/vmdk: add eos marker for stream optimized disks
block/vmdk.c | 121 ++++++++++++++++++++++++++++++++-------------------
1 file changed, 76 insertions(+), 45 deletions(-)
--
2.55.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v3 1/3] block/vmdk: align stream optimized data grains on 512 byte chunks
2026-10-16 12:37 [PATCH v3 0/3] block/vmdk: fixes for stream optimized disks Sanne Raymaekers
@ 2026-10-16 12:37 ` Sanne Raymaekers
2026-10-16 12:37 ` [PATCH v3 2/3] block/vmdk: skip RGD for stream optimized disks Sanne Raymaekers
2026-10-16 12:37 ` [PATCH v3 3/3] block/vmdk: add eos marker " Sanne Raymaekers
2 siblings, 0 replies; 4+ messages in thread
From: Sanne Raymaekers @ 2026-10-16 12:37 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-block, fam, kwolf, hreitz, Sanne Raymaekers
For stream optimized disks the spec notes: "Each marker and its
associated block begin on a sector or 512‐byte boundary... There are
five types of markers: compressed grain markers,..." [^1]
This means that instead of aligning grains on their typical size (64K),
in stream optimized disks, grains should be aligned on 512 byte chunks.
[^1] Virtual Disk Format 5.0 Technical Note.
Signed-off-by: Sanne Raymaekers <sraymaek@redhat.com>
---
block/vmdk.c | 27 ++++++++++++++++++---------
1 file changed, 18 insertions(+), 9 deletions(-)
diff --git a/block/vmdk.c b/block/vmdk.c
index cd8b4ec7c8..7ca030c5fe 100644
--- a/block/vmdk.c
+++ b/block/vmdk.c
@@ -1730,16 +1730,25 @@ get_cluster_offset(BlockDriverState *bs, VmdkExtent *extent,
cluster_sector = extent->next_cluster_sector;
extent->next_cluster_sector += extent->cluster_sectors;
- /* First of all we write grain itself, to avoid race condition
- * that may to corrupt the image.
- * This problem may occur because of insufficient space on host disk
- * or inappropriate VM shutdown.
+ /*
+ * Stream optimized (compressed) extents do not have to be
+ * pre-allocated, as compressed writes always cover the whole
+ * cluster, and will align on 512 byte chunks.
*/
- ret = get_whole_cluster(bs, extent, cluster_sector * BDRV_SECTOR_SIZE,
- offset, skip_start_bytes, skip_end_bytes,
- zeroed);
- if (ret) {
- return ret;
+ if (!extent->compressed) {
+ /*
+ * First of all we write grain itself, to avoid race condition
+ * that may to corrupt the image.
+ * This problem may occur because of insufficient space on host disk
+ * or inappropriate VM shutdown.
+ */
+ ret = get_whole_cluster(bs, extent,
+ cluster_sector * BDRV_SECTOR_SIZE,
+ offset, skip_start_bytes, skip_end_bytes,
+ zeroed);
+ if (ret) {
+ return ret;
+ }
}
if (m_data) {
m_data->new_allocation = true;
--
2.55.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH v3 2/3] block/vmdk: skip RGD for stream optimized disks
2026-10-16 12:37 [PATCH v3 0/3] block/vmdk: fixes for stream optimized disks Sanne Raymaekers
2026-10-16 12:37 ` [PATCH v3 1/3] block/vmdk: align stream optimized data grains on 512 byte chunks Sanne Raymaekers
@ 2026-10-16 12:37 ` Sanne Raymaekers
2026-10-16 12:37 ` [PATCH v3 3/3] block/vmdk: add eos marker " Sanne Raymaekers
2 siblings, 0 replies; 4+ messages in thread
From: Sanne Raymaekers @ 2026-10-16 12:37 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-block, fam, kwolf, hreitz, Sanne Raymaekers
For stream optimized disks the spec notes: "The rgdOffset should be
ignored because bit 1 of the flags field is not set." Where bit 1 of the
flags field indicates a redundant grain directory (RGD).[^1]
In short, stream optimized disks should have the RGD offset set to 0,
and only write the main grain directory.
This patch also corrects the comments around the code writing the grain
directories, and it makes the error thrown when it fails to write the
backup grain directory more explicit.
[^1] Virtual Disk Format 5.0 Technical Note.
Signed-off-by: Sanne Raymaekers <sraymaek@redhat.com>
---
block/vmdk.c | 46 ++++++++++++++++++++++++++++++----------------
1 file changed, 30 insertions(+), 16 deletions(-)
diff --git a/block/vmdk.c b/block/vmdk.c
index 7ca030c5fe..e090e122f1 100644
--- a/block/vmdk.c
+++ b/block/vmdk.c
@@ -2246,8 +2246,9 @@ vmdk_init_extent(BlockBackend *blk, int64_t filesize, bool flat, bool compress,
} else {
header.version = 1;
}
- header.flags = VMDK4_FLAG_RGD | VMDK4_FLAG_NL_DETECT
- | (compress ? VMDK4_FLAG_COMPRESS | VMDK4_FLAG_MARKER : 0)
+ header.flags = VMDK4_FLAG_NL_DETECT
+ | (compress ?
+ VMDK4_FLAG_COMPRESS | VMDK4_FLAG_MARKER : VMDK4_FLAG_RGD)
| (zeroed_grain ? VMDK4_FLAG_ZERO_GRAIN : 0);
header.compressAlgorithm = compress ? VMDK4_COMPRESSION_DEFLATE : 0;
header.capacity = filesize / BDRV_SECTOR_SIZE;
@@ -2262,8 +2263,16 @@ vmdk_init_extent(BlockBackend *blk, int64_t filesize, bool flat, bool compress,
header.desc_offset = 1;
header.desc_size = 20;
- header.rgd_offset = header.desc_offset + header.desc_size;
- header.gd_offset = header.rgd_offset + gd_sectors + (gt_size * gt_count);
+
+ if (compress) {
+ header.rgd_offset = 0;
+ header.gd_offset = header.desc_offset + header.desc_size;
+ } else {
+ header.rgd_offset = header.desc_offset + header.desc_size;
+ header.gd_offset =
+ header.rgd_offset + gd_sectors + (gt_size * gt_count);
+ }
+
header.grain_offset =
ROUND_UP(header.gd_offset + gd_sectors + (gt_size * gt_count),
header.granularity);
@@ -2303,21 +2312,26 @@ vmdk_init_extent(BlockBackend *blk, int64_t filesize, bool flat, bool compress,
goto exit;
}
- /* write grain directory */
+ /* write backup grain directory */
gd_buf_size = gd_sectors * BDRV_SECTOR_SIZE;
gd_buf = g_malloc0(gd_buf_size);
- for (i = 0, tmp = le64_to_cpu(header.rgd_offset) + gd_sectors;
- i < gt_count; i++, tmp += gt_size) {
- gd_buf[i] = cpu_to_le32(tmp);
- }
- ret = blk_co_pwrite(blk, le64_to_cpu(header.rgd_offset) * BDRV_SECTOR_SIZE,
- gd_buf_size, gd_buf, 0);
- if (ret < 0) {
- error_setg_errno(errp, -ret, "failed to write VMDK grain directory");
- goto exit;
+
+ if (!compress) {
+ for (i = 0, tmp = le64_to_cpu(header.rgd_offset) + gd_sectors;
+ i < gt_count; i++, tmp += gt_size) {
+ gd_buf[i] = cpu_to_le32(tmp);
+ }
+ ret = blk_co_pwrite(blk,
+ le64_to_cpu(header.rgd_offset) * BDRV_SECTOR_SIZE,
+ gd_buf_size, gd_buf, 0);
+ if (ret < 0) {
+ error_setg_errno(errp, -ret,
+ "failed to write VMDK backup grain directory");
+ goto exit;
+ }
}
- /* write backup grain directory */
+ /* write grain directory */
for (i = 0, tmp = le64_to_cpu(header.gd_offset) + gd_sectors;
i < gt_count; i++, tmp += gt_size) {
gd_buf[i] = cpu_to_le32(tmp);
@@ -2326,7 +2340,7 @@ vmdk_init_extent(BlockBackend *blk, int64_t filesize, bool flat, bool compress,
gd_buf_size, gd_buf, 0);
if (ret < 0) {
error_setg_errno(errp, -ret,
- "failed to write VMDK backup grain directory");
+ "failed to write VMDK grain directory");
}
ret = 0;
--
2.55.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH v3 3/3] block/vmdk: add eos marker for stream optimized disks
2026-10-16 12:37 [PATCH v3 0/3] block/vmdk: fixes for stream optimized disks Sanne Raymaekers
2026-10-16 12:37 ` [PATCH v3 1/3] block/vmdk: align stream optimized data grains on 512 byte chunks Sanne Raymaekers
2026-10-16 12:37 ` [PATCH v3 2/3] block/vmdk: skip RGD for stream optimized disks Sanne Raymaekers
@ 2026-10-16 12:37 ` Sanne Raymaekers
2 siblings, 0 replies; 4+ messages in thread
From: Sanne Raymaekers @ 2026-10-16 12:37 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-block, fam, kwolf, hreitz, Sanne Raymaekers
The end of stream marker should be at the very end of a stream optimized
disk. It consists of a regular marker struct with `val`, `size` and
`type` set to 0. It is then padded with more 0s to fill out a
sector. This effectively results in 512 zero bytes.[^1]
[^1] Virtual Disk Format 5.0 Technical Note.
Signed-off-by: Sanne Raymaekers <sraymaek@redhat.com>
---
block/vmdk.c | 48 ++++++++++++++++++++++++++++--------------------
1 file changed, 28 insertions(+), 20 deletions(-)
diff --git a/block/vmdk.c b/block/vmdk.c
index e090e122f1..b1848130a0 100644
--- a/block/vmdk.c
+++ b/block/vmdk.c
@@ -195,6 +195,13 @@ typedef struct VmdkGrainMarker {
uint8_t data[];
} QEMU_PACKED VmdkGrainMarker;
+typedef struct EosMarker {
+ uint64_t val;
+ uint32_t size;
+ uint32_t type;
+ uint8_t pad[512 - 16];
+} QEMU_PACKED EosMarker;
+
enum {
MARKER_END_OF_STREAM = 0,
MARKER_GRAIN_TABLE = 1,
@@ -987,12 +994,7 @@ vmdk_open_vmdk4(BlockDriverState *bs, BdrvChild *file, int flags,
VMDK4Header header;
uint8_t pad[512 - 4 - sizeof(VMDK4Header)];
- struct {
- uint64_t val;
- uint32_t size;
- uint32_t type;
- uint8_t pad[512 - 16];
- } QEMU_PACKED eos_marker;
+ EosMarker eos_marker;
} QEMU_PACKED footer;
ret = bdrv_pread(file, bs->file->bs->total_sectors * 512 - 1536,
@@ -2182,23 +2184,29 @@ vmdk_co_pwritev_compressed(BlockDriverState *bs, int64_t offset, int64_t bytes,
QEMUIOVector *qiov)
{
if (bytes == 0) {
- /* The caller will write bytes 0 to signal EOF.
- * When receive it, we align EOF to a sector boundary. */
+ /* The caller will write bytes 0 to signal EOF. */
BDRVVmdkState *s = bs->opaque;
- int i, ret;
+ int ret;
int64_t length;
+ QEMU_AUTO_VFREE EosMarker *eos_marker = NULL;
- for (i = 0; i < s->num_extents; i++) {
- length = bdrv_co_getlength(s->extents[i].file->bs);
- if (length < 0) {
- return length;
- }
- length = QEMU_ALIGN_UP(length, BDRV_SECTOR_SIZE);
- ret = bdrv_co_truncate(s->extents[i].file, length, false,
- PREALLOC_MODE_OFF, 0, NULL);
- if (ret < 0) {
- return ret;
- }
+ /* Stream optimized disks should only contain a single extent. */
+ assert(s->num_extents == 1);
+
+ /*
+ * Stream optimized disks end with an EOS marker,
+ * consisting of 512 null bytes.
+ */
+ eos_marker = qemu_blockalign0(bs, sizeof(EosMarker));
+ length = bdrv_co_getlength(s->extents[0].file->bs);
+ if (length < 0) {
+ return length;
+ }
+ length = QEMU_ALIGN_UP(length, BDRV_SECTOR_SIZE);
+ ret = bdrv_co_pwrite(s->extents[0].file, length, sizeof(EosMarker),
+ eos_marker, 0);
+ if (ret < 0) {
+ return ret;
}
return 0;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-16 12:39 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-16 12:37 [PATCH v3 0/3] block/vmdk: fixes for stream optimized disks Sanne Raymaekers
2026-10-16 12:37 ` [PATCH v3 1/3] block/vmdk: align stream optimized data grains on 512 byte chunks Sanne Raymaekers
2026-10-16 12:37 ` [PATCH v3 2/3] block/vmdk: skip RGD for stream optimized disks Sanne Raymaekers
2026-10-16 12:37 ` [PATCH v3 3/3] block/vmdk: add eos marker " Sanne Raymaekers
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.