Skip to content

nvx-rs: serve shorter images, and restore clones in RAM without a scratch file - #925

Merged
Pedro Henrique Penna (ppenna) merged 2 commits into
devfrom
esaurez/848-shorter-images-absent-scratch-20261026
Oct 29, 2026
Merged

Pedro Henrique Penna (ppenna) merged 2 commits into
devfrom
esaurez/848-shorter-images-absent-scratch-20261026

Conversation

@esaurez

@esaurez Enrique Saurez (esaurez) commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Part of #848.

Stacked on #963.

Chain: #951 → #954 → #957 → #959 → #960 → #955 → #958 → #962 → #963 → this PR, on dev; #879, #881, #885, #886, #877, #932, #884, #887, #933, #880, #936, #937, #939, #940, #941, #888, #949, and #950 have merged.

What and why

Until now, a clone's image had to be exactly as long as the template's placeholder, and each clone in RAM got a sparse scratch file of the template's scratch length, which it never used. OpenVMM now presents the distro capacity that a platform snapshot recorded over a shorter image, reading zeros past its end, and lets a restore declare a fresh scratch absent (microsoft/openvmm#4749, in dev's OpenVMM pin since #901). So one template now serves images of any length up to its capacity, in either format, and clones in RAM restore with no scratch file at all.

Images shorter than a template's capacity

ClonePlan::new admits an image no longer than the template's placeholder (TemplateKey::distro), with its logical and physical block sizes, and refuses, before any VMM starts:

  • an image of other block sizes, by the evidence supplied-geometry, whatever its length;
  • a longer image, by the evidence supplied-length: "the image PATH is N bytes long, longer than the template's placeholder, whose M bytes are the capacity of every clone's image".

A file whose length is not a nonzero multiple of 512 bytes stays refused, by supplied-geometry. On Linux a file's physical block size is its filesystem's I/O block size (st_blksize), so the image must lie on a filesystem of the placeholder's; on Windows it is always 4096 bytes. A scratch image keeps the exact rule: the template's scratch geometry, its length included. Nothing reads, pads, or resizes the image.

Clones in RAM without a scratch file

Every restore attaches the template's scratch block device, which the template's guest negotiated. For a clone in RAM, the restore now passes --restore-absent-scratch and no scratch block device, so OpenVMM attaches the scratch at the recorded geometry with no file: reads return zeros, writes fail, and the clone's guest, whose upper is a tmpfs, never uses it. A clone over a scratch image still passes the image as the scratch block device. So the host creates nothing for a clone in RAM:

  • CloneUpper::Tmpfs is now a unit variant, and ClonePlan::scratch_path() returns Option<&Path>, None in RAM.
  • SandboxStore::clone_scratch_path() is gone, and nothing creates the clone's scratch file, clone-scratch.raw, any more. The store still counts its name, nvx::host::store::CLONE_SCRATCH_NAME, among its files, FILE_NAMES, and among the files of a clone that a release removes, so that a release and a removal remove one that an earlier version left for a clone in RAM, which a removal would otherwise report as foreign. A plan still refuses it as an image, as it refuses every file of the sandbox's store.
  • A launch no longer checks that the scratch file of a clone in RAM is still there, as the launch of nvx-rs: build a clone's activation record and launch its restore #949 did: under --restore-absent-scratch, a clone in RAM has no scratch file, so the check guards nothing.
  • A template whose scratch presented physical blocks of another size than the sandbox's filesystem now serves clones in RAM, since no file of that filesystem backs their scratch.

A template's capture keeps its own dormant scratch file, since a cold boot cannot declare it absent.

The guest

No change, as #848 expects. For a GPT disk, the guest asks the kernel to reread its partition table before it assembles the root (#845), and nvx::gpt requires only that the primary header's alternate LBA lie inside the device (read_header in nvx-rs/src/gpt/mod.rs), which holds for a disk shorter than the device; it never reads the backup header. What a clone's activation record expects of a GPT disk, its GUID and a bound on its first partition, concerns the disk's primary header and first partition, which a shorter disk keeps. A clone in RAM looks for no scratch device, and its upper, a tmpfs that the assembly mounts empty, always passes the check that a clone's upper is fresh, which still refuses a used upper on a scratch image.

Tests

nvx-rs unit tests:

  • Images of either format of 512 bytes, of half and of all but 512 bytes of the placeholder's length, and of its length, are served; longer ones of either format are refused, by supplied-length, and ragged lengths by supplied-geometry. A template whose placeholder presented other physical blocks serves no image of this filesystem, whatever its length.
  • On Linux hosts that offer huge pages, files on hugetlbfs, whose physical blocks are 2 MiB: an image as long as the placeholder or shorter, and a scratch image, refused by supplied-geometry.
  • A clone in RAM is planned with no file in the sandbox's directory, and its VMM's arguments hold --restore-absent-scratch and no scratch block device; a clone over a scratch image has no such flag.
  • A template whose scratch presented other physical blocks serves clones in RAM, and refuses scratch images of this filesystem.
  • A clone-scratch.raw that an earlier version left is refused as an image. The store's tests of a removal and of a release's retried removal stage it among a clone's files, and remove it.
  • a_clone_launches_only_as_the_launch_that_its_durable_binding_reserves no longer launches a stale plan in RAM whose scratch file a later, failed plan removed: a clone in RAM has no scratch file, so that section guarded nothing, and the test's other sections stay. a_release_makes_the_removal_of_its_binding_durable_before_it_removes_its_record now stages the record of the clone's failure in place of that file, as a file of the clone that a release removes only once it ended the lock's reservation.

Runtime adapter VM scenarios, in vm_clone, on every backend:

  • one_generation_serves_clones_of_either_format_over_either_upper: the GPT template's placeholder is now 16 MiB longer than the platform's EROFS image, so its four clones at once, the EROFS image over a scratch image and in RAM, a GPT disk of the EROFS image's length in RAM, and one as short as its layout allows over a scratch image, whose first partition meets the clone's bound exactly, are all shorter than the capacity. Each finds its image's disk at the capacity and its scratch device at the template's scratch length, and reads both to their ends. No file in the sandbox of a clone in RAM is as long as the template's scratch, and on Linux its VMM's arguments hold --restore-absent-scratch and no scratch block device.
  • a_start_refuses_what_the_template_cannot_serve_before_any_vmm_starts: an EROFS image and a GPT disk 4 KiB longer than the placeholder are refused, by supplied-length, in place of the image 4 KiB shorter that the exact rule refused.

The other scenarios' clones in RAM restore the same way, with no scratch file.

An image on a filesystem of another block size has no VM scenario: no CI host offers the suite a second filesystem of another I/O block size without a privileged mount, and Windows presents 4096-byte physical blocks for every file. The nvx-rs tests above cover that refusal.

Guide (#591's numbering)

This PR leaves doc/design unchanged; #591 takes:

  • 2.1.3: a platform capture's distro presents the placeholder's length, the capacity of every image that a restore supplies, apart from the image's own length; and every block of a snapshot-capable machine is backed by a file, except a fresh scratch that a restore declares absent.
  • 3.4.4: the same exception to "Each block must then be a nonempty, 512-byte-aligned raw regular file", and, on Linux, a substitute layer's physical block size is its filesystem's, so it must lie on a filesystem of the recorded physical block size.
  • 3.4.5 (the fresh policy) and 5.5.2: a restore may declare a fresh scratch absent, for a guest that never uses it, such as one whose overlay upper is in RAM: the device presents the recorded geometry, reads return zeros, and writes fail.
  • 5.4.7 (Table 5.4, sandbox block): besides "a new scratch file of the same geometry", none, for a fresh scratch declared absent.

Validation

At this PR's head 99fe9a6, the same two commits as the earlier head ce7932d, restacked onto #963's head 41a4181, which is #963 rebased onto dev at f8469b8. ce7932d held the same two commits as the earlier head 1c77b97; the rest of this section describes 1c77b97, on #963's earlier head 918e6fc.

At 1c77b97, on #963's head 918e6fc:

  • Windows: nvx.py verify; the steps of check-nvx-rs that run there (fmt; clippy with all features, of the library, and without features; tests with all and default features, and of the library without features; the check of artifact bundling; docs with -D warnings; MSRV 1.89); the OpenVMM vectors test; the runtime adapter's protocol-only and host-only configurations; test-runtime-adapter --check-only; ruff and the Markdown link check; and validate-nvx's Python suites, 1425 tests, run with Git for Windows' bash, and its adversarial, host-connect, and --help checks: all pass.
  • Linux (x86_64): the same steps of check-nvx-rs, plus the macOS target check, check-guest-binaries, build-guest-binaries and test-guest-binaries, and the runtime adapter's guest-only configuration; as root, the 13 tests that need it, in guest::clone, guest::root, and guest::mappings; Specula's tests; and validate-nvx's Python suites, but for the 24 tests that list the files of a Git checkout, which the tree there, an export, was not, and which pass on Windows: all pass.
  • The runtime adapter's suite of 155 scenarios in 11 targets, nvx.py test-runtime-adapter, on WHP, one test at a time, and on Linux with MSHV, as root: all pass on each. OpenVMM is the pin, 832d5bb4, from the development release of 2d14547, with that release's kernel and Ubuntu EROFS layer; the initramfs and the guest agent are built from this head.
  • CI, a manual full run on 1c77b97, https://github.com/microsoft/nvx/actions/runs/40847433894: 36 jobs passed, among them every job on KVM, MSHV, and WHP, whose guests and sandboxes each pass the runtime adapter's 155 scenarios, and 6 were skipped: the performance gate and the rehearsal of guest artifact reuse, which run for pull requests alone, and the performance baseline, the development release, and the nvx crate's packaging and publication, which run for dev alone.
  • Reviews: Copilot found no issues at 1c77b97. A local review of the rebase onto runtime-adapter: start and run from a template, and run the stage's scenarios as one suite #963, in three rounds, found one sentence of ClonePlan::launch's documentation that still described the check of a clone's scratch file in RAM, which this head removes; its other rounds found nothing.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The stacked restore changes cross device and guest boundaries, with the final rebased head and shorter-image/absent-scratch VM scenarios still requiring validation on KVM, MSHV, and WHP.

No open issues 🎉

What changed in this PR

This PR implements #848 in the NVX host library, using OpenVMM’s zero-fill support so one template can serve shorter images and restore RAM-backed clones without creating scratch files.

Changes:

  • Accept images up to the template’s capacity while preserving block-size checks and exact scratch-image geometry requirements.
  • Replace RAM clones’ sparse scratch files with --restore-absent-scratch, updating the public API and store cleanup.
  • Expand unit and VM scenarios for shorter images, oversized-image refusals, and file-free scratch devices; update documentation.
File Description
tests/​runtime-adapter/​tests/​vm_clone.rs Tests shorter images, presented capacities, and absent scratch backing.
tests/​runtime-adapter/​src/​clone.rs Documents the relaxed image-length contract.
tests/​runtime-adapter/​src/​bin/​runtime-adapter-host.rs Updates command documentation for shorter images and RAM uppers.
tests/​runtime-adapter/​README.md Documents restore behavior and revised VM scenarios.
nvx-rs/​src/​host/​template/​store/​leases/​tests.rs Removes obsolete scratch-file cleanup expectations.
nvx-rs/​src/​host/​template/​store/​leases.rs Updates lease-release documentation.
nvx-rs/​src/​host/​template/​store.rs Documents restores with no scratch backing file.
nvx-rs/​src/​host/​template/​clone/​tests/​launch.rs Checks absent-scratch arguments and scratch-image launches.
nvx-rs/​src/​host/​template/​clone/​tests.rs Tests capacity admission, geometry refusals, and file-free RAM plans.
nvx-rs/​src/​host/​template/​clone/​launch.rs Adds the absent-scratch restore flag for RAM uppers.
nvx-rs/​src/​host/​template/​clone.rs Relaxes image-length validation and removes RAM scratch-file creation.
nvx-rs/​src/​host/​store/​tests.rs Updates clone-file cleanup tests.
nvx-rs/​src/​host/​store/​mod.rs Removes the clone scratch filename, path API, and cleanup entry.
nvx-rs/​src/​host/​store/​lifecycle/​tests.rs Updates removal tests for the reduced store file set.
nvx-rs/​src/​host/​sandbox/​distro.rs Documents image capacity and block-size requirements.
nvx-rs/​README.md Updates the host-library overview for both restore changes.

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The draft depends on an unmerged base, with shorter-image restores and absent-scratch behavior still awaiting KVM, MSHV, and WHP validation and review of the final rebased diff.

No open issues 🎉

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues were identified for the template-clone changes.

No open issues 🎉

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues remain for the template-cloning changes.

No open issues 🎉

Review effort: Balanced

@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/848-shorter-images-absent-scratch-20261026 branch from f663a1f to 1c77b97 Compare October 29, 2026 09:01
@esaurez
Enrique Saurez (esaurez) changed the base branch from esaurez/848-base-snapshot-20261028 to esaurez/847-pr2-start-run-suite-20261024 October 29, 2026 09:01
@esaurez
Enrique Saurez (esaurez) requested a balanced review from Copilot October 29, 2026 09:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Current-head VM validation remains pending across KVM, MSHV, and WHP for shorter GPT devices and absent-scratch restores that span host, OpenVMM, and guest-kernel contracts.

No open issues 🎉

Review effort: Balanced

@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/848-shorter-images-absent-scratch-20261026 branch from 1c77b97 to ce7932d Compare October 29, 2026 15:39
@esaurez Enrique Saurez (esaurez) removed their assignment Oct 29, 2026
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the esaurez/847-pr2-start-run-suite-20261024 branch from f43428e to 41a4181 Compare October 29, 2026 19:49
Enrique Saurez and others added 2 commits October 29, 2026 12:58
…atch file

A template's restore presents the capacity that its placeholder fixed for
the `distro` block device, and OpenVMM reads zeros past the end of a
shorter image, so a clone's image need no longer be exactly the
placeholder's length. `ClonePlan` admits an image of either format no
longer than the placeholder, with its logical and physical block sizes,
and refuses a longer one, or one of other block sizes, as before any VMM
starts. On Linux a file's physical block size is the I/O block size of
its filesystem, so the image must lie on a filesystem of the
placeholder's. A scratch image still presents the template's scratch
exactly.

The restore of a clone in RAM declares the template's scratch absent,
with `--restore-absent-scratch`, so that OpenVMM attaches it with no
file: its reads return zeros and its writes fail, and the clone's guest
never uses it. The host no longer creates the sparse `clone-scratch.raw`
in the sandbox's directory, nor does a launch check it:
`CloneUpper::Tmpfs` holds nothing, `ClonePlan::scratch_path` returns
`None` for a clone in RAM, and the sandbox store loses
`clone_scratch_path`. The store still counts `CLONE_SCRATCH_NAME` among
its files, so that a release and a removal remove one that an earlier
version left for a clone in RAM.

The guest needs no change: it rereads a GPT disk's partition table
before it assembles the root, `nvx::gpt` requires only that a disk's
backup header lie inside the device, and a clone in RAM looks for no
scratch device.

The runtime adapter's GPT template now has a placeholder 16 MiB longer
than the platform's image, so the clones of either format that it serves
are all shorter than the capacity: they find their image's disk at the
capacity and their scratch device at the template's scratch length, and
read each to its end; nothing in the sandbox of a clone in RAM backs its
scratch device, and on Linux its VMM declares the scratch absent. A start
refuses an EROFS image and a GPT disk longer than the placeholder, in
place of the shorter image that the exact length refused.

Part of #848.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 053388a9-90ee-4632-8cd4-711de08d267e
The template module's overview still said that a clone's plan refuses
an image of another length than the placeholder, `SuppliedLength` that
a supplied file has another length than the template's device, and
`IncompatibleHost` that the sandbox's filesystem may present another
geometry than the template's scratch, which only the removed sparse
scratch file could. Describe the capacity rule instead: an image longer
than the placeholder, or of other block sizes, and a scratch image of
another length than the template's scratch. The README's launch also
names `--restore-absent-scratch` for a clone in RAM.

Part of #848.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 053388a9-90ee-4632-8cd4-711de08d267e
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/848-shorter-images-absent-scratch-20261026 branch from ce7932d to 99fe9a6 Compare October 29, 2026 19:59
Base automatically changed from esaurez/847-pr2-start-run-suite-20261024 to dev October 29, 2026 20:29
@ppenna
Pedro Henrique Penna (ppenna) merged commit cc7b8b3 into dev Oct 29, 2026
2 checks passed
@ppenna
Pedro Henrique Penna (ppenna) deleted the esaurez/848-shorter-images-absent-scratch-20261026 branch October 29, 2026 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants