Every VHD ends with a 512-byte footer that describes the disk. A fixed disk is the raw
disk followed by this footer; dynamic and differencing disks also keep a copy of it at
the very start of the file, so that a damaged end of file can be recovered. The format
is defined by Microsoft’s Virtual Hard Disk Image Format Specification
(version 1.0, 2006), which is not a PDF and was not read for this page; the layout here
is checked against Joachim Metz’s libvhdi documentation, Virtual Hard Disk (VHD)
image format, and QEMU’s VHD driver (block/vpc.c). All numbers are
big-endian.
Fields
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
0 | 8 | text | Cookie | conectix, from Connectix, who made the format. |
8 | 4 | uint32 | Features | Bit flags: see features. |
12 | 4 | uint32 | File format version | 0x00010000: major version 1 in the high 16 bits, minor 0 in the low. |
16 | 8 | uint64 | Data offset | Where the next structure starts, from the beginning of the file: the dynamic disk header, normally 512. A fixed disk has 0xFFFFFFFFFFFFFFFF. |
24 | 4 | uint32 | Time stamp | Seconds since 1 January 2000 00:00:00 UTC (libvhdi calls it the modification time). |
28 | 4 | text | Creator application | A four-character code for the program that made the image: see creator codes. |
32 | 4 | uint32 | Creator version | That program’s version, major in the high 16 bits, minor in the low. |
36 | 4 | text | Creator host OS | Wi2k (0x5769326B) for Windows or Mac (0x4D616320) for Macintosh. |
40 | 8 | uint64 | Original size | A disk size in bytes (QEMU: orig_size; libvhdi: disk size). |
48 | 8 | uint64 | Current size | The disk size in bytes that QEMU uses unless the creator is vpc or qemu (see creator codes). |
56 | 2 | uint16 | Cylinders | Disk geometry: see geometry. |
58 | 1 | uint8 | Heads | – |
59 | 1 | uint8 | Sectors per track | – |
60 | 4 | uint32 | Disk type | See disk types. |
64 | 4 | uint32 | Checksum | See checksum. |
68 | 16 | UUID | Unique ID | A big-endian UUID identifying the disk. A differencing disk’s header names its parent by this ID. |
84 | 1 | uint8 | Saved state | Set when the image is in a saved state. |
85 | 427 | – | Reserved | Zeros. |
Features
| Bit | Name | Meaning |
|---|---|---|
0x00000001 | Temporary | The disk is a candidate for deletion on shutdown. |
0x00000002 | Reserved | Must always be set. |
All other bits are reserved and 0, so the usual value is 0x00000002.
Creator codes
The codes listed by libvhdi and by the comment in QEMU’s block/vpc.c.
QEMU takes the disk size from the geometry for vpc and
qemu, and from the current size for the rest.
| Code | Bytes | Program |
|---|---|---|
vpc | 76 70 63 20 | Microsoft Virtual PC |
vs | 76 73 20 20 | Microsoft Virtual Server |
win | 77 69 6E 20 | Windows: Disk Management (libvhdi), Hyper-V (QEMU) |
d2v | 64 32 76 00 | Disk2vhd |
qemu | 71 65 6D 75 | QEMU, size from the geometry |
qem2 | 71 65 6D 32 | QEMU, size from the current size |
tap | 74 61 70 00 | XenServer |
CTXS | 43 54 58 53 | XenConverter |
wa | 77 61 00 00 | Microsoft Azure |
Disk types
| Value | Type |
|---|---|
| 0 | None |
| 1 | Reserved (deprecated) |
| 2 | Fixed: the whole disk, then the footer |
| 3 | Dynamic: grows as the disk is written; see dynamic disks |
| 4 | Differencing: holds only changes from a parent VHD |
| 5 | Reserved (deprecated) |
| 6 | Reserved (deprecated) |
Checksum
The ones’ complement (all bits inverted) of the sum of all the bytes of the footer without the checksum field (libvhdi). The dynamic disk header has a checksum worked out in the same way over its own 1,024 bytes.
Geometry
The cylinders, heads and sectors per track (libvhdi: used by an ATA controller to
size the disk). This is QEMU’s calculate_geometry(), which its comment
describes as “the algorithm described in the VHD specification”, working from the
size in 512-byte sectors; because it rounds down, the disk can be slightly smaller
than the size asked for.
- Let total be the size in sectors, at most 65,535 × 16 × 255 (QEMU’s
VHD_MAX_GEOMETRY). - If total ≥ 65,535 × 16 × 63: sectors per track = 255, heads = 16, ch = total ÷ 255.
- Otherwise: sectors per track = 17, ch = total ÷ 17, heads
= (ch + 1023) ÷ 1024, at least 4.
- If ch ≥ heads × 1024 or heads > 16: sectors per track = 31, heads = 16, ch = total ÷ 31.
- Then, if ch ≥ heads × 1024: sectors per track = 63, heads = 16, ch = total ÷ 63.
- Cylinders = ch ÷ heads. All divisions drop the remainder.
Example
The first 96 bytes of the footer of a fixed 64 MiB disk made by Windows, at the end of the file (offset 0x4000000). The rest is zeros.
04000000 63 6F 6E 65 63 74 69 78 00 00 00 02 00 01 00 00 conectix........
04000010 FF FF FF FF FF FF FF FF 2C E6 AD 80 77 69 6E 20 ........,...win
04000020 00 0A 00 00 57 69 32 6B 00 00 00 00 04 00 00 00 ....Wi2k........
04000030 00 00 00 00 04 00 00 00 03 C3 08 11 00 00 00 02 ................
04000040 FF FF E8 80 5B 2E 9F 1C 7A 44 4E 0B 9D 33 6C 81 ....[...zDN..3l.
04000050 F0 2A 11 E7 00 00 00 00 00 00 00 00 00 00 00 00 .*..............
Reading it: cookie conectix; features 2; version 1.0; data offset all
FF, as for a fixed disk; time stamp 0x2CE6AD80,
753,315,200 seconds after 2000, which is 14 November 2023 22:13:20 UTC; creator
win version 10.0 on Wi2k; both sizes
0x4000000, 64 MiB; geometry 963 cylinders (0x03C3), 8
heads, 17 sectors per track, which the algorithm above gives for 131,072 sectors;
disk type 2, fixed; checksum 0xFFFFE880; then the unique ID and saved
state 0.