Check¶
instar check validates image structural integrity, as a drop-in
replacement for qemu-img check.
# Validate image structural integrity (matches qemu-img check output)
instar check image.qcow2
# JSON output for programmatic consumption
instar check --output json image.qcow2
# Validate the entire backing chain
instar check --chain image.qcow2
# Repair a qcow2 image in place: safe leak reclamation (the default tier)
instar check --repair image.qcow2
# Lossy repair: also rebuild refcounts and reconcile COPIED flags
instar check --repair=all image.qcow2
QCOW2 validation¶
For QCOW2 images, check validates:
- Header integrity (version, cluster_bits, virtual_size)
- Incompatible feature bit validation (rejects unknown bits per spec)
- Full L1/L2 table consistency (all sectors, including extended L2)
- Extended L2 subcluster bitmap validation (alloc/zero overlap, alloc-without-host, host-without-ref, compressed non-zero bitmap)
- Overlap detection (two L2 entries referencing same host cluster)
- Refcount validation for all widths (1/2/4/8/16/32/64-bit refcounts)
- Leak detection (clusters with refcount > 0 but no L2 reference), including correct handling of compressed cluster host ranges
- Cluster sizes from 512B to 2MB (cluster_bits 9-21)
- Dirty/corrupt incompatible feature flags
QCOW2 repair¶
For QCOW2 images, instar check --repair[=leaks|all] additionally repairs the
image in place: the safe leaks tier reclaims unreferenced clusters
(crash-safe, lossless), and the lossy all tier rebuilds refcounts and
reconciles COPIED flags under a crash-safe corrupt-bit write ordering —
mirroring qemu-img check -r leaks/-r all. It refuses rather than guessing:
snapshotted images are repaired by neither tier, and the lossy all tier
declines its rebuild on compressed, external-data, or already-corrupt images
(the safe leak reclamation still runs, and the result is reported incomplete).
See
qcow2/qcow2-refcount.md.
VMDK validation¶
For VMDK images (monolithicSparse, streamOptimized, and monolithicFlat — including multi-extent twoGbMaxExtentFlat and flat-in-backing-chain via parentFileNameHint), check validates:
- Full header parsing (version, capacity, grain size, flags, compression)
- Descriptor bounds and multi-extent detection (graceful rejection)
- Grain directory offset within file bounds
- Full grain directory and grain table walk
- Grain data offsets within file bounds
- Compressed grain marker validation (LBA consistency, compressed size bounds, marker-plus-data within file)
- Redundant Grain Directory (RGD) cross-check when FLAG_USE_RGD is set
- Overlap detection via 1-bit-per-grain bitmap (same pattern as QCOW2)
- streamOptimized footer validation (magic, GD offset)
- Fragmentation measurement
VHD validation¶
For VHD images (dynamic and fixed), check validates:
- Footer cookie and checksum (from first or last sector)
- Format version (must be 1.0) and features (reserved bit required)
- Disk type validity (fixed or dynamic; a differencing VHD is refused rather than validated — see "Differencing images" below)
- Fixed VHD: data_offset check, file size vs virtual size validation
- Dynamic header cookie, checksum, and version
- BAT offset within file bounds
- BAT entry validation: allocated block offsets within file
- Overlap detection (no two BAT entries reference same block)
- Fragmentation tracking (non-sequential block allocation)
- Footer copy consistency (start vs end of file)
VHDX validation¶
For VHDX images, check validates:
- File identifier signature at offset 0 ("vhdxfile")
- Header 1 and Header 2: signature, CRC-32C checksum, active header selection by sequence number
- Dirty log detection (non-zero log GUID)
- Region table 1: signature, CRC-32C, BAT and metadata region presence
- Region table 2: cross-validation against region table 1
- Metadata: required items (FileParameters, VirtualDiskSize, LogicalSectorSize, PhysicalSectorSize)
- Differencing disk detection — refused rather than validated, see "Differencing images" below
- BAT entries: block offsets within file bounds, 1MB alignment, overlap detection, state validation
- A payload block naming file offset zero, or a byte range
overlapping any region the image's own region table declares (the
BAT region, the metadata region, or one this build does not
recognise), is counted as a corruption rather than validated as
data — the same test
convert,dd,compare,benchandrebasealready apply when reading such a block, applied here againstcheck's own parsed region table so the two agree. See "Malformed block tables" below. - Fragmentation tracking (non-sequential block allocation)
Chain validation¶
The --chain flag discovers the full backing chain (using the same chain
discovery infrastructure as instar info --chain), sets up each image as a
separate virtio-block device in the KVM guest, and validates:
- Format consistency: each backing image's format matches what chain discovery found
- Virtual size validity: backing images must have non-zero virtual size
- QCOW2 header validation: backing images that are QCOW2 get basic header checks (magic, version, cluster_bits, L1/refcount table bounds, corrupt feature flag)
Chain errors are reported separately as chain-errors in JSON output and
in human-readable output. Without --chain, chain-errors is always 0.
Note: The chain-errors field is always present in JSON output,
even when --chain is not used. This is a schema addition relative to
previous versions.
Differencing images¶
A differencing VHD (footer disk type 4) or VHDX (HasParent set) holds
only the sectors that differ from a parent image. check reads an image
on its own rather than composing a parent chain into it, so it
refuses such an image instead of validating it:
$ instar check vhdx-diff-child.vhdx
check: source is a differencing VHDX image, and check reads an image on
its own rather than composing a parent into it, so the sectors it leaves
to its parent could not be composed
$ echo $?
1
This is unchanged by --chain: a differencing source is refused whether
or not the chain flag is given, since the refusal happens before any
chain is walked.
Two things about that are worth knowing if you script against check:
- The exit code is 1, not 2. Exit 2 means "corruption found"; a
differencing image is not corrupt, it is unsupported, so it takes the
ordinary failure code. A differencing VHDX previously exited 2 with
"1 errors were found on the image.", and a differencing VHD previously exited 0 having validated only the child's own blocks. --output jsonproduces no JSON document. The refusal happens before there is a result to serialise, so stdout is empty and the message goes to stderr. Every othercheckoutcome, including corruption, still writes a JSON object.
convert, dd, compare, bench and rebase do compose a
differencing source against its parent; see the "VHD/VHDX differencing"
section of quirks.md for the per-operation record.
This refusal is deliberate, not merely unimplemented: check validates
one image's structure, and a differencing child's own structure is
almost always intact while the image as a whole is unusable without
its parent — a clean verdict would describe a partial view as though
it were the whole image. Whether that should change anyway is tracked
as #643, open and
not settled either way.
Malformed block tables¶
Separately from the differencing refusal above, check counts a
non-differencing VHDX as corrupt when its BAT names a payload block
that is the image's own structure: file offset zero, where the file
identifier and headers live, or a byte range overlapping any region
the image's region table declares. Before this, check's own BAT and
region-table validation never cross-checked a block's offset against
the regions it had just parsed, so such a block claimed an
overlap-bitmap slot nothing else held and the image came back clean:
(--verbose additionally traces "[GUEST] check: VHDX block inside a
declared region", or "[GUEST] check: VHDX block at file offset
zero" for the other ground; the default output above only ever
reports the count.) This is unchanged by --chain. The same two
conditions are what map and measure refuse outright rather than
count, and what convert, dd, compare, bench and rebase
already refused to read before check gained this check — see
docs/map.md § Malformed block tables
and docs/measure.md § Malformed block tables.