instar create — create a new empty disk image¶
instar create produces a new empty disk image in a target format. It is
the safe, sandboxed equivalent of qemu-img create: for every supported
format, qemu-img info --output=json on an instar create-produced
image matches qemu-img info on the corresponding qemu-img create
image, modulo a documented divergence whitelist (filename, actual-size,
vmdk per-invocation CIDs, vhdx log-size).
Raw output is host-only — open(O_CREAT|O_TRUNC) + ftruncate plus
optional preallocation — so it does not require KVM. A monolithicFlat
VMDK is written on the host too (see below). Every other target
format runs the create.bin guest in the KVM sandbox and writes the
metadata via virtio-block.
Synopsis¶
Common options:
-f, --format <FMT> raw | qcow2 | vmdk | vpc | vhdx
Default: raw
-b, --backing <BACKING> Backing file path (embedded verbatim)
-F, --backing-format <FMT> Backing file format hint
-u, --backing-unsafe Don't fail if backing isn't accessible
-q, --quiet Suppress "Created: ..." line on success
--output <FORMAT> human (default) | json
-o, --options <KEY=VALUE,...> qemu-img-style options (repeatable)
--preallocation <MODE> off | metadata | falloc | full
Per-target option flags (alternatives to -o):
qcow2: --cluster-size, --refcount-bits, --extended-l2,
--lazy-refcounts, --compat
vmdk: --subformat, --grain-size
vpc: --subformat, --block-size
vhdx: --block-size
SIZE accepts a suffix from {b,K,M,G,T} (e.g. 1G, 512M). It is
required unless -b BACKING is given, in which case it defaults to the
backing file's virtual size. A monolithicFlat VMDK cannot have a backing
file, so it always needs SIZE.
The full flag surface is reported by instar create --help.
Target formats¶
| Target | Subformats | Backing? | qemu-img info equivalence |
|---|---|---|---|
| raw | (host-only) | No | byte-equivalent (file size + zero-fill) |
| qcow2 | n/a | Yes | info-equivalent (modulo refcount_bits, compat, zstd) |
| vmdk | monolithicSparse, streamOptimized, monolithicFlat | Yes (not monolithicFlat) | info-equivalent |
| vpc | dynamic, fixed | Yes (dynamic only) | info-equivalent (modulo CHS virtual_size rounding) |
| vhdx | dynamic | Yes | info-equivalent |
A "No" in the Backing? column means the format cannot be created
as a child at all: -b / -o backing_file is rejected. Raw is the
only such target — it has no metadata to record a backing reference
in. vpc and vhdx may be used as the parent of a qcow2 or vmdk
child regardless of format, which is what tests/test_create.py's
test_vhdx_as_backing exercises; they may now also be used as a
child of a parent of their own format — see the per-format
sections below. vpc's "Yes" is qualified because a differencing VHD
is necessarily subformat=dynamic: only a dynamic disk has the
header and BAT a parent reference lives in, so -o subformat=fixed
together with -b is refused. A fixed VHD is fine as the parent
of a differencing child.
The "info-equivalence" contract is verified by the cross-version baseline
matrix in instar-testdata/expected-outputs/create-info-json/ across 80
qemu-img versions (6.0.0 through 10.2.0), exercised by
tests/test_create.py's TestCreateBaselineMatrix.
Output format¶
Human (default):
JSON (--output=json):
{
"format": "qcow2",
"filename": "foo.qcow2",
"virtual_size": 1073741824,
"resolved_unit_size": 65536,
"metadata_bytes_written": 196608,
"file_size_after": 196608
}
-q (quiet) suppresses both forms on success and only prints errors.
-o key=value,... reference¶
Repeatable; later -o invocations override earlier ones. Individual
flag values are overridden by matching -o keys when both are
specified.
qcow2¶
Honoured:
- size (alternative to the positional SIZE)
- cluster_size (512..2 MiB power of two)
- refcount_bits — see Known divergences
- extended_l2 (on|off; requires cluster_size >= 16k)
- lazy_refcounts (on|off)
- compat (0.10|1.1) — see Known divergences
- compression_type (zlib|zstd) — see Known divergences
- preallocation (off|metadata|falloc|full)
- backing_file, backing_fmt (alternative to -b / -F)
Rejected (future work):
- data_file, data_file_raw, encrypt.*
vmdk¶
Honoured:
- size, subformat (monolithicSparse|streamOptimized|monolithicFlat)
- backing_file, backing_fmt (not with monolithicFlat)
- preallocation (off|falloc|full; falloc and full are no-ops, metadata is refused)
- zeroed_grain (ignored for the sparse subformats; on is refused for
monolithicFlat, as qemu-img refuses it)
Accepted and ignored: adapter_type, hwversion, toolsversion.
Rejected (instar doesn't emit these subformats):
- subformat=twoGbMaxExtent*
Note: qemu-img doesn't accept an -o grain_size= key — grain is set by
the subformat. instar exposes --grain-size as an independent flag, and
also accepts -o grain_size. Neither is valid for monolithicFlat.
monolithicFlat¶
-o subformat=monolithicFlat and --subformat monolithicFlat are the
same request. The image is two files, written on the host with no guest:
- a raw extent, sized with
ftruncatetoSIZErounded up to a whole 512-byte sector (a 1000-byte request gives a 1024-byte extent, and a virtual size of 1024), and - the text descriptor at
FILENAME, naming the extent.
The extent is named the way qemu-img names it: the basename of
FILENAME is split at its last . and the extent is
<prefix>-flat<postfix> in the same directory (out.vmdk gives
out-flat.vmdk, out.img gives out-flat.img, out gives out-flat).
instar convert uses the same rule. Both files are replaced if they
exist.
The extent is sparse under every preallocation mode. qemu-img only
truncates it too, so falloc and full write what off writes. The
descriptor is the one instar convert writes, without qemu's DDB lines
and with a constant CID (see quirks.md).
Refused, before either file is opened:
| Request | Message |
|---|---|
-b or -o backing_file |
create: vmdk monolithicFlat cannot have a backing file |
no SIZE |
create: vmdk monolithicFlat requires SIZE |
-o zeroed_grain=on |
create: vmdk monolithicFlat cannot enable zeroed_grain |
--grain-size or -o grain_size |
create: vmdk monolithicFlat has no grains, so it takes no grain size (got N) |
preallocation=metadata |
create: preallocation=metadata is not supported for vmdk |
an extent name holding ", CR or LF |
create: vmdk monolithicFlat cannot record the extent name "..." in its descriptor (it must be UTF-8 without '"', CR or LF) |
qemu-img refuses the first, third and last too ("Flat image can't have backing file", "Flat image can't enable zeroed grain", "Unsupported preallocation mode: metadata"); it has no grain size option at all. It writes any extent name verbatim (see quirks.md). On the metadata refusal qemu-img leaves a zero-byte descriptor behind; instar leaves nothing. If a failure happens after a file has been opened (the extent's name is taken by a directory, or the extent cannot be sized), instar removes every file it opened.
On success, --output json reports the descriptor as filename, its
length as both metadata_bytes_written and file_size_after, and
resolved_unit_size 0.
vpc (VHD)¶
Honoured:
- size, subformat (dynamic|fixed)
- backing_file, backing_fmt (alternative to -b / -F) — a VHD
child with a parent is a differencing disk (disk_type=4 plus a
populated parent locator entry in the dynamic header). The guest
reads the parent's identity — footer uuid and timestamp — off
the parent itself and writes it into the child. The parent must
itself be a VHD: format detection decides, not the -F hint, and a
parent that detects as anything else (or a hint that contradicts
detection) is refused with ERROR_PARENT_FORMAT_MISMATCH. Either
subformat of VHD is acceptable as the parent — a fixed VHD carries
its conectix cookie only in the trailing footer, and the parent
probe reads the footer rather than trusting the first sector. The
child, though, is necessarily dynamic: subformat=fixed with a
backing file is refused ("invalid option for target format"),
because a fixed VHD has no dynamic header to record a parent in.
See Backing-file semantics below and the
"VHD/VHDX differencing" section of quirks.md.
preallocation(off|falloc|full;fallocandfullare no-ops,metadatais refused)
Accepted but no size effect:
- force_size
vhdx¶
Honoured:
- size, at most 64 TiB (70368744177664 bytes), the largest VHDX
qemu-img creates. A larger size is refused before the output file is
opened, so nothing is left behind (qemu-img leaves an empty file):
$ instar create -f vhdx big.vhdx 65T
Error: "create: virtual size 71468255805440 exceeds the VHDX maximum of 64 TiB (70368744177664 bytes)"
A size taken from a backing file is checked in the guest and refused
with backing file's virtual size exceeds the VHDX maximum of 64 TiB.
A size that is not a multiple of 512 is rounded up to one, as qemu-img
does, because VirtualDiskSize must be a multiple of the logical
sector size: 1073741825 records 1073742336, and the Created:
line reports the recorded size.
- block_size (1 MiB..256 MiB power of two). Unset, it is qemu-img's
default for the size: 8 MiB up to 1 GiB, 16 MiB up to 100 GiB, 32 MiB
up to 32 TiB, then 64 MiB. The Created: line's unit_size reports
the block size the image records.
- preallocation (off|falloc|full; falloc and full are no-ops, metadata is refused)
- backing_file, backing_fmt — a VHDX child with a parent sets the
HasParent file-parameter bit and writes a populated parent
locator metadata item carrying the parent's active-header
DataWriteGuid. The parent must itself be a VHDX, checked the same
way as vpc above: detection decides, and a mismatch (or a
contradicting -F) is refused with ERROR_PARENT_FORMAT_MISMATCH.
See Backing-file semantics below and the
"VHD/VHDX differencing" section of quirks.md.
Accepted but no size effect:
- log_size
raw¶
Honoured:
- size, preallocation (off|falloc|full)
Rejected:
- preallocation=metadata (raw has no metadata to preallocate)
- Any backing-file key (raw doesn't support backing)
Backing-file semantics¶
This section describes create for the target formats that accept a
backing file: qcow2, vmdk, vpc and vhdx. Raw has no way to record one.
The user-typed path is embedded verbatim into the new image's metadata,
matching qemu-img: a relative path stays relative; an absolute path
stays absolute. This holds for qcow2, vmdk, and for the VHD parent
unicode name field, which is the field qemu and libvhdi resolve a VHD
parent through. It does not hold for the vpc/vhdx locator path
that records where a Hyper-V tool would look for the parent: a
relative path is normalised into the Hyper-V convention before being
written there (/ becomes \, prefixed .\), while a POSIX-absolute
path is kept verbatim under the Windows-defined locator key, because
no honest Windows-style rendering of an absolute POSIX path exists.
See the "VHD/VHDX differencing" section of quirks.md for
the rationale and the resulting path-length limits. The host resolves
the path relative to the new image's directory when opening the
backing file, so the resulting reference is portable across moves of
the parent.
A backing file requires either -F FMT (explicit format hint) or -u
(unsafe; skip the accessibility check). -u is not a shorthand for
-F raw — it asserts nothing about the format at all. See
-u is not -F raw below, which is the difference
that decides how a fixed VHD parent is sized. The hint is used as the
initial format guess; if
the backing file's first sector contradicts the hint via its magic
bytes, auto-detection wins and the metadata records the detected
format. vpc and vhdx additionally require the parent to be their own
format — a vpc child needs a VHD parent, a vhdx child a VHDX one — and
refuse with ERROR_PARENT_FORMAT_MISMATCH otherwise, whether the
mismatch comes from detection or from an explicit -F that
contradicts it; qcow2 and vmdk are unaffected and continue to accept a
parent of any format they can detect. Three-level chains record only
the immediate parent — instar does not recurse to grandparents
(matches qemu-img). When the backing size would exceed the target
format's addressable range, ERROR_BACKING_SIZE_TOO_LARGE fires with
an actionable "try a larger cluster size" hint.
Every check reached through the backing probe — the differencing
refusal below, the parse check and format detection — now runs
whenever -b is given, including when an explicit SIZE is also
given. Previously create -b <parent> child 64M skipped all three
checks because the probe only ran to infer a missing size (#579).
Running the probe always does not mean every backing image must
be one the probe can size. It parses raw, qcow2, sparse VMDK, VHD
(either subformat) and VHDX; for anything else — vdi, qcow1, qed,
iso, luks, parallels, bochs, cloop, the VMDK v3 header, a VMDK text
descriptor as monolithicFlat produces, or a block device that
stats as zero-length — it reports the format it detected and no
size. That is only fatal where a size is actually needed: omit
SIZE and the create is refused, supply one and a qcow2 or vmdk
child is written as before, since those record a parent by path and
never ask it how big it is. A vpc or vhdx child is refused either
way — but with a diagnosis that distinguishes the two cases: a parent
of the wrong format is ERROR_PARENT_FORMAT_MISMATCH, while a parent
of the right format whose deeper structures will not parse (a VHD
missing its trailing footer, a VHDX with an unreadable region table)
is ERROR_BACKING_PARSE_FAILED, since telling the user their VHD is
not a VHD would be both false and unactionable.
-u is not -F raw¶
-u is not a synonym for -F raw here. A fixed-subformat VHD keeps
no copy of its footer at offset 0, so header detection alone calls one
raw; instar falls back to reading the file's last sector for a VHD
footer, as info, check and resize all do, and sizes the parent
from current_size if it finds one. Any -F that positively asserts
a format suppresses that fallback — -F raw included, and -F vpc
excepted because it agrees with it — because that is the user
asserting what the file is, and it is also what the child records as
its backing format, so sizing from a footer while writing raw would
have the two disagree by the footer's 512 bytes. Detection reports
raw for anything it does not recognise as well as for a genuinely
raw file, so without this rule a raw parent whose last 512 bytes
happen to begin with conectix would be sized from those bytes even
under -F qcow2. -u asserts nothing about the
format (it only says "do not fail if the backing file is
inaccessible"), so the fallback still applies: create -f qcow2 -b
fixed.vhd -u child.qcow2 sizes the parent from its VHD footer, while
-F raw on the same file sizes it from the file length. -u also
lets a fixed VHD be accepted as a vpc differencing parent without the
user naming a format.
A relative parent path is rewritten into a POSIX-equivalent form
rather than kept byte-for-byte: redundant leading ./ components and
repeated separators are collapsed before / is mapped to \\, so
-b ./sub//parent.vhdx emits the locator .\\sub\\parent.vhdx, and a
path that collapses to nothing (./) is refused. For vhdx this is
user-visible on the way back out, because the locator is the only
record of the path and so is what info reports: the child above
reports sub/parent.vhdx. A VHD child reports the path exactly as
typed, since info reads its parent unicode name field instead.
A differencing child inherits its parent's virtual size, and a
SIZE that disagrees with the parent is refused with
ERROR_PARENT_SIZE_MISMATCH rather than written. The child stores only
the blocks that differ and reads every other block from the parent at
the same offset, so a chain whose two images describe different disks
cannot be composed. Omitting SIZE is the right way to ask for a
differencing child. This bites most easily with a qemu-img-created
parent: qemu-img rounds a VHD's virtual size up to CHS geometry and
instar does not, so a parent qemu-img made as 64M declares
67,125,248 bytes and -b parent.vhd ... 64M is a mismatch.
Two rules apply to the parent path a vpc or vhdx child records, both covered in full in the "VHD/VHDX differencing" section of quirks.md:
- A relative path containing a literal backslash is refused with
ERROR_PARENT_PATH_NOT_REPRESENTABLE. The locator holds a Windows path, so instar writes/as\when filling it, and a backslash already in the filename cannot be told apart from one instar produced. An absolute path keeps its POSIX bytes and is not subject to the rule. - A relative path caps two code units shorter than an absolute one,
because the
.\prefix comes out of the same budget: 254 versus 255 for VHD, 258 versus 260 for VHDX. Over the cap isERROR_PARENT_NAME_TOO_LONG.
A differencing VHD or VHDX is refused as a backing file:
create failed: backing file is a differencing VHD or VHDX; create does
not support stacking a backing file on one
This has its own error code (ERROR_BACKING_DIFFERENCING) rather than
the generic ERROR_BACKING_PARSE_FAILED, because the backing header
parses perfectly well — the image is valid, create simply does not
support building an overlay on top of one. Their plain dynamic parents
are accepted normally.
Note that convert, dd, compare, bench and rebase do compose a
differencing VHD or VHDX against its parent elsewhere in instar, so an
overlay created outside instar on such a base — with qemu-img rebase -u,
for example — is readable through those operations today; this refusal is
create's own restriction on what it will build, not a statement that
instar cannot read the result. See the "VHD/VHDX differencing" section
of quirks.md.
Preallocation modes¶
| Mode | raw | qcow2 | vmdk / vpc / vhdx |
|---|---|---|---|
off |
ftruncate only |
header + L1 + refcount only | header + BAT only (monolithicFlat: a sparse extent) |
metadata |
rejected | L1 + L2 + refcount populated; data region as a hole | rejected |
falloc |
posix_fallocate |
metadata mode + posix_fallocate on data region |
accepted, same as off |
full |
fallocate(ZERO_RANGE) with pwrite fallback |
metadata mode + zero-fill data region | accepted, same as off |
For qcow2 metadata mode the L2 tables can total well over the guest's
8 MiB scratch limit at large virtual sizes, so the guest streams them
through a reusable single-cluster slot rather than packaging them into
the MetadataPlan. The host's apply_preallocation helper handles
falloc and full post-emission.
falloc and full on vmdk, vpc and vhdx are accepted and change nothing: the
image is the one off writes. That is qemu-img's behaviour, not a shortcut.
None of the three drivers lists a preallocation create option (see
vpc_create_opts in qemu 7.0.0's block/vpc.c); the key is consumed by the
file protocol that creates the image file (raw_co_create_opts() in
block/file-posix.c), which preallocates the empty file before the driver
writes anything. The committed tests/golden/qemu-create-pins.json records,
for all 80 qemu-img builds from 6.0.0, that falloc and full images equal
the off image in content and in allocated blocks. metadata is refused
("Unsupported preallocation mode: metadata", from handle_aiocb_truncate()
in block/file-posix.c), and instar refuses it too, with
create: preallocation=metadata is not supported for FORMAT. By contrast,
instar resize --preallocation falloc|full on these formats does allocate
the newly appended region (see format-coverage.md).
Known divergences from qemu-img create¶
The cross-version baseline matrix passes for the bulk of supported
options, but four categories of writer divergence are documented and
tracked as future work. The canonical list (with per-case rationale)
for the first three is KNOWN_WRITER_DIVERGENCES in
tests/test_create.py; the fourth has no entry there because there is
no baseline to skip — qemu-img cannot produce the output at all.
- VHD
virtual_size: qemu-img rounds up to the next CHS-aligned multiple (legacy VHD geometry); instar emits exact bytes. The divergence is typically < 256 KiB. Both files are valid VHDs; the difference surfaces only inqemu-img info'svirtual-sizefield. - qcow2
compat=0.10: the writer always emitscompat=1.1. qemu-img honours both. - qcow2
compression_type=zstd: the writer recordszlibin the header regardless. qemu-img switches over to the zstd cluster encoder when zstd is requested. The cluster data itself is empty in a fresh image so the field-only divergence has no functional impact, butqemu-img inforeports the discrepancy. - vpc / vhdx differencing children: instar can create a
differencing VHD or VHDX (
-bwith-f vpc/-f vhdx); qemu-img refuses to create either ("Backing file not supported for file format 'vpc'" /'vhdx'), so this is an instar-only capability with no qemu-img oracle to compare against. See the "VHD/VHDX differencing" section of quirks.md.
All -o refcount_bits= widths (1/2/4/8/16/32/64) are now written
correctly: build_header derives refcount_order from refcount_bits
and sub-byte widths pack LSB-first, so the produced files round-trip
through instar check and match qemu-img (instar #365).
Future work¶
- VHD CHS-geometry round-trip matching (close the
virtual_sizedivergence). - qcow2
compat=0.10honouring. - zstd-aware qcow2 create (drop the accept-ignore; emit the zstd header bit).
- The split VMDK subformats (
twoGbMaxExtentSparse,twoGbMaxExtentFlat). - qemu's DDB lines (
ddb.virtualHWVersion,ddb.geometry.*,ddb.adapterType,ddb.toolsVersion) in VMDK descriptors, honouringadapter_type,hwversionandtoolsversion. - VHDX
subformat=fixed. instar refuses it today, but qemu-img does emit it:qemu-img create -f vhdx -o subformat=fixedsets theLeaveBlocksAllocatedfile parameter, writes a BAT entry for every payload block (PAYLOAD_BLOCK_ZEROby default,PAYLOAD_BLOCK_FULLY_PRESENTat sequential offsets with-o block_state_zero=off) and extends the file sparsely to the end of its data region. --object OBJDEFandencrypt.*for LUKS-encrypted create — pair with the existing convert-side passphrase plumbing.- Atomic-rename safety: write to
FILENAME.tmpandrename()on guest success, so a guest crash mid-emission doesn't leave a partial file. - Refactor convert to call into
crates/create/for the metadata emission step (master-plan future-work item).
For the per-format metadata layouts implemented today, see
src/crates/create/src/lib.rs (the plan_qcow2 / plan_vmdk /
plan_vhd / plan_vhdx functions). For the divergence-whitelist
applied during JSON comparison, see tests/helpers/info_json.py.
Examples¶
Create a 1 GiB qcow2 with default options:
Same but with a 4 KiB cluster size and extended L2:
Create a vmdk with the stream-optimised subformat:
Create a 16 MiB fixed VHD (footer-only metadata at end of file):
Create a 1 GiB vhdx. With no block_size it gets qemu-img's default
for the size, 8 MiB here; the default rises to 16 MiB above 1 GiB,
32 MiB above 100 GiB and 64 MiB above 32 TiB (see
quirks.md):
Create a vhdx with an explicit 32 MiB block size:
Create a qcow2 child image with a backing reference (virtual_size defaults from the parent):
Create a differencing VHD child (the parent's identity is read off the parent and the virtual size defaults from it):
Create a raw image with falloc preallocation (reserves blocks
without writing them):
JSON output for scripting: